Profiles

Connect Credential Platforms

Connect Bluesky and other credential-based platforms

Supported Platforms

  • Bluesky - App password-based (currently available)
  • Additional platforms coming soon

Bluesky Connection

Bluesky uses app passwords for third-party authentication. Unlike OAuth, this is a direct credential exchange where users generate a password specifically for your application.

Understanding App Passwords

App passwords are Bluesky's way of allowing third-party apps to access accounts without sharing the main password. They:

  • Are generated in Bluesky settings, not by the user
  • Follow the format xxxx-xxxx-xxxx-xxxx (16 characters with hyphens)
  • Never expire unless manually revoked by the user
  • Can be named to help users remember which app they're for
  • Can be revoked individually without affecting the main account

User Flow

When a user wants to connect their Bluesky account to your app:

  1. Direct them to Bluesky settings: Send users to bsky.app/settings/app-passwords

  2. They create an app password:

    • Click "Add App Password"
    • Name it (e.g., "YourAppName" or "My Social Scheduler")
    • Copy the generated password immediately (it won't be shown again)
  3. They provide credentials to your app: User enters both their handle and the app password in your interface

  4. Your app connects via postcore: Send the credentials to postcore's API

  5. Connection complete: The account is now connected and ready for posting

Implementation

Endpoint:

POST /profiles/connect

Request:

    const response = await fetch('https://api.postcore.dev/profiles/connect', {
      method: 'POST',
      headers: {
        'x-api-key': process.env.POSTCORE_API_KEY,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        profileKey: 'prof_abc123', // Or omit to auto-create
        platform: 'bluesky',
        credentials: {
          handle: 'user.bsky.social',
          appPassword: 'abcd-efgh-ijkl-mnop'
        }
      }),
    });
    
    const data = await response.json();
    console.log('Connected:', data.username);
    response = requests.post(
        'https://api.postcore.dev/profiles/connect',
        headers={
            'x-api-key': os.getenv('POSTCORE_API_KEY'),
            'Content-Type': 'application/json'
        },
        json={
            'profileKey': 'prof_abc123',  # Or omit to auto-create
            'platform': 'bluesky',
            'credentials': {
                'handle': 'user.bsky.social',
                'appPassword': 'abcd-efgh-ijkl-mnop'
            }
        }
    )
    
    data = response.json()
    print(f"Connected: {data['username']}")
    curl -X POST https://api.postcore.dev/profiles/connect \
      -H "x-api-key: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "profileKey": "prof_abc123",
        "platform": "bluesky",
        "credentials": {
          "handle": "user.bsky.social",
          "appPassword": "abcd-efgh-ijkl-mnop"
        }
      }'

Request Body:

FieldTypeRequiredDescription
profileKeystringNo*Profile to connect to (*auto-created if omitted)
platformstringYesMust be "bluesky"
credentials.handlestringYesBluesky handle (e.g., user.bsky.social)
credentials.appPasswordstringYesApp password from Bluesky settings

Response:

{
  "profileKey": "prof_abc123",
  "platform": "bluesky",
  "username": "user.bsky.social"
}

Status Code: 201 Created

Important Notes

Handle Format: The handle must include the full domain (e.g., user.bsky.social). Common custom domains work too (e.g., user.customdomain.com).

Storage: postcore stores app passwords encrypted. Your application never needs to store the app password - just save the profileKey returned from the connection.

Auto-Creating Profiles: If you omit the profileKey parameter, postcore will automatically create a new profile and return its profileKey in the response. Save this with your user's account.

Expiry: App passwords never expire unless the user manually revokes them in their Bluesky settings. Monitor the needsReconnect field in the profile data to detect if a password has been revoked.

UI Recommendations

When building your connection interface:

  1. Provide clear instructions: Link directly to bsky.app/settings/app-passwords and explain the steps

  2. Show the expected format: Display placeholder text xxxx-xxxx-xxxx-xxxx in the password field

  3. Explain why: Tell users this is for security - it's not their main password and can be revoked anytime

  4. Name suggestion: Recommend they name it after your app so they remember what it's for

  5. Handle validation: Check that the handle includes a domain (.bsky.social or custom domain)


Error Responses

Invalid Credentials

{
  "error": "INVALID_CREDENTIALS",
  "message": "Invalid Bluesky credentials"
}

Status: 401 Unauthorized

Common causes:

  • Wrong handle format (missing .bsky.social or domain)
  • App password was revoked or doesn't exist
  • Typo in handle or password
  • Password copied incorrectly (includes spaces or wrong characters)

Profile Not Found

{
  "error": "PROFILE_NOT_FOUND",
  "message": "Profile not found"
}

Status: 404 Not Found

Solution: The specified profileKey doesn't exist. Either create the profile first or omit profileKey to auto-create.

Wrong Method for OAuth

{
  "error": "USE_GET_FOR_OAUTH",
  "message": "linkedin requires OAuth. Use GET /profiles/connect?platform=linkedin&apiKey=YOUR_KEY&returnUrl=YOUR_URL"
}

Status: 400 Bad Request

Solution: This endpoint is for credential-based platforms only. For OAuth platforms like LinkedIn, see Connect OAuth Platforms.