Skip to main content

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_KEY
    • Content-Type: application/json
  • Body Parameters:

    NameTypeRequiredDescription
    usernameStringYesA 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 invalid username.
    • 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 provided username already 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: true when 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:

FieldWhat it isUse it to
usernameThe 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.
handleThe public @name on the platform.Show a human which account a post goes to.
display_nameThe 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 — handle follows the rename on the next reconnection, username never moves,
  • it does differ if a different TikTok account is connected. A changed username therefore means "this is another account", which is exactly the signal you want before publishing.
Reading it back

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

CapabilityWhat it unlocks
musicAttach 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.
locationTag a place on the post: tiktok_location_id + tiktok_location_name. See Get TikTok Locations.
cover_imageSet a custom cover image: tiktok_cover_image_url / tiktok_cover_image.
cover_timestampPick the cover frame with cover_timestamp (milliseconds).
draftSend the video to the account's drafts with post_mode=MEDIA_UPLOAD (alias tiktok_upload_to_draft=true).
photo_privacyThe connection accepts privacy_level on photo posts.
video_privacyThe connection accepts privacy_level on video posts. Connections without it always publish videos as public — use draft instead.
inbox_fallbackWhen 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.
commentsEverything 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_searchGET /suggestions?type=keywords: the terms people search around a word. Only granted on reconnection — see below.
profile_analyticsProfile-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 reconnection

Every 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.

Treat capabilities as an open list

New 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

ParameterTypeDescription
usernamestringRequired. 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_KEY
    • Content-Type: application/json
  • Body Parameters:

    NameTypeRequiredDescription
    usernameStringYesThe 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 invalid username.
    • 401 Unauthorized: Invalid or missing API Key.
    • 404 Not Found: No profile found with the provided username.

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_KEY
    • Content-Type: application/json
  • Body Parameters:

    NameTypeRequiredDescription
    usernameStringYesThe identifier for the user profile for which the JWT is being generated.
    redirect_urlStringNo(Optional) The URL to which the user will be redirected after linking their social account.
    logo_imageStringNo(Optional) A URL to a logo image to display on the linking page for branding purposes.
    redirect_button_textStringNo(Optional) The text to display on the redirect button after linking. Defaults to "Logout connection".
    connect_titleStringNo(Optional) Custom title text for the connection page. Defaults to "Connect Social Media Accounts".
    connect_descriptionStringNo(Optional) Custom description text for the connection page. Defaults to "Connect your social media accounts to manage your posts.".
    platformsArrayNo(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_calendarBooleanNo(Optional) Whether to show the calendar view on the connection page. Defaults to true.
    readonly_calendarBooleanNo(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 to false.
    languageStringNo(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_labelsObjectNo(Optional) Flat object of i18n key → replacement string, to override individual pieces of connect-page UI text. See Custom UI Labels.
  • Supported languages:

    ValueLanguage
    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: Always true if 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/calendar while keeping the token intact, e.g. https://app.upload-post.com/connect/calendar?token=GENERATED_JWT_TOKEN. The page will automatically fall back to /connect when the profile has show_calendar disabled. When readonly_calendar is true, the user is automatically redirected to the calendar view regardless of the URL path.

  • Error Responses:

    • 400 Bad Request: Missing or invalid username, or an invalid ui_labels payload (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 provided username (error_code: PROFILE_NOT_FOUND).
  • Integration tip: If JWT generation returns 404, call GET /api/uploadposts/users first 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

RuleLimit
Maximum number of entries100
Key formatMust match ^[a-zA-Z0-9_.]+$ — letters, numbers, dots and underscores only
Value typeMust be a string
Value lengthMaximum 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 sendResult
The field is omittedPreviously stored labels are left untouched
"ui_labels": { ... } (non-empty)Replaces the stored labels with what you sent
"ui_labels": nullClears 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.

warning

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 Authorization header, 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., null if 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 via generate-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 the Authorization header.

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:

    NameTypeRequiredDescription
    profileStringNoThe 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_KEY
    • Content-Type: application/json
  • Body Parameters:

    NameTypeRequiredDescription
    profile_usernameStringYesThe profile to link the Discord webhook to.
    webhook_urlStringYesThe Discord channel incoming webhook URL (https://discord.com/api/webhooks/...).
    nameStringNoOptional 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.discord is set to the connection key (the supplied name, or the webhook id when name is omitted).

  • Error Responses:

    • 400 Bad Request: Missing profile_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_KEY
    • Content-Type: application/json
  • Body Parameters:

    NameTypeRequiredDescription
    profile_usernameStringYesThe profile to link the Telegram bot to.
    bot_tokenStringYesThe bot token from @BotFather (e.g. 123456:ABC-DEF...).
    chat_idStringYesThe target chat: a public channel @username, or a numeric chat id (e.g. -100123456789).
    nameStringNoOptional 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.telegram is set to the connection key (the supplied name, or the bot username when name is omitted).

  • Error Responses:

    • 400 Bad Request: Missing profile_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.