Backend Integration Guide

This guide explains how to integrate Outstand into your backend and manage social media IDs in your database. By the end, you'll have a clear picture of the data flow and a recommended schema for storing Outstand resources.

Integration Overview

Key Concepts

Outstand has three main resources you need to track in your backend:

ResourceWhat it representsWhen you get it
Social NetworkYour OAuth app configuration (e.g., your X app, your Facebook app)When you call POST /v1/social-networks
Social AccountA user's connected account (e.g., @john on X)When a user completes the OAuth flow
PostA piece of content published to one or more accountsWhen you call POST /v1/posts

Step-by-Step Integration

1. Store your OAuth credentials

Register your OAuth app credentials with Outstand. You typically do this once per social platform.

curl -X POST https://api.outstand.so/v1/social-networks \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "network": "x",
    "client_key": "your_oauth_client_id",
    "client_secret": "your_oauth_client_secret"
  }'

Save the returned id - this is your socialNetworkId. Store it in your database mapped to the platform name.

2. Connect user accounts

When a user wants to connect their social account, get an authorization URL and redirect them:

curl -X GET https://api.outstand.so/v1/social-networks/{socialNetworkId}/auth-url \
  -H "Authorization: Bearer YOUR_API_KEY"

After the user authorizes, Outstand handles the OAuth callback. The new social account becomes available via the API (and via webhook if configured).

3. Retrieve and store social account IDs

After a user connects their account, list your social accounts to get the new entry:

curl -X GET https://api.outstand.so/v1/social-accounts \
  -H "Authorization: Bearer YOUR_API_KEY"

Save the returned id for each account. This socialAccountId is what you'll use to create posts.

4. Create posts

Use the stored socialAccountIds to publish content:

curl -X POST https://api.outstand.so/v1/posts \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "containers": [
      { "content": "Hello world!" }
    ],
    "socialAccountIds": ["acc_123", "acc_456"]
  }'

Save the returned postId and the per-account platformPostIds for tracking publish status and analytics.

Here's a recommended schema for storing Outstand resource IDs alongside your own data. Adapt this to your ORM and database.

SQL Example

-- Map your users/orgs to Outstand social networks
CREATE TABLE social_networks (
  id SERIAL PRIMARY KEY,
  outstand_network_id VARCHAR(255) NOT NULL,  -- ID from Outstand
  platform VARCHAR(50) NOT NULL,              -- 'x', 'facebook', 'instagram', etc.
  created_at TIMESTAMP DEFAULT NOW()
);

-- Map connected accounts to your users
CREATE TABLE social_accounts (
  id SERIAL PRIMARY KEY,
  user_id INTEGER NOT NULL REFERENCES users(id),
  outstand_account_id VARCHAR(255) NOT NULL,  -- ID from Outstand
  platform VARCHAR(50) NOT NULL,
  username VARCHAR(255),
  connected_at TIMESTAMP DEFAULT NOW()
);

-- Track posts and their publish status
CREATE TABLE posts (
  id SERIAL PRIMARY KEY,
  user_id INTEGER NOT NULL REFERENCES users(id),
  outstand_post_id VARCHAR(255) NOT NULL,     -- ID from Outstand
  content TEXT,
  scheduled_at TIMESTAMP,
  created_at TIMESTAMP DEFAULT NOW()
);

-- Track per-account publish results
CREATE TABLE post_accounts (
  id SERIAL PRIMARY KEY,
  post_id INTEGER NOT NULL REFERENCES posts(id),
  outstand_account_id VARCHAR(255) NOT NULL,
  platform_post_id VARCHAR(255),              -- Native platform post ID
  status VARCHAR(20) DEFAULT 'pending',       -- 'pending', 'published', 'failed'
  error TEXT,
  published_at TIMESTAMP
);

TypeScript Example (Prisma)

model SocialNetwork {
  id                String   @id @default(cuid())
  outstandNetworkId String   @unique
  platform          String
  createdAt         DateTime @default(now())
}

model SocialAccount {
  id                String   @id @default(cuid())
  userId            String
  outstandAccountId String   @unique
  platform          String
  username          String?
  connectedAt       DateTime @default(now())
  user              User     @relation(fields: [userId], references: [id])
}

model Post {
  id              String        @id @default(cuid())
  userId          String
  outstandPostId  String        @unique
  content         String?
  scheduledAt     DateTime?
  createdAt       DateTime      @default(now())
  user            User          @relation(fields: [userId], references: [id])
  postAccounts    PostAccount[]
}

model PostAccount {
  id                String    @id @default(cuid())
  postId            String
  outstandAccountId String
  platformPostId    String?
  status            String    @default("pending")
  error             String?
  publishedAt       DateTime?
  post              Post      @relation(fields: [postId], references: [id])
}

Keeping Data in Sync

Configure webhooks to receive real-time updates. This is the best approach for keeping your database in sync:

  • post.published - Update post status and store platformPostId
  • post.error - Record errors for failed publishes
// Example webhook handler
app.post('/webhooks/outstand', async (req, res) => {
  const event = req.body;

  switch (event.event) {
    case 'post.published':
      for (const account of event.data.socialAccounts) {
        await db.postAccount.update({
          where: { outstandAccountId: account.id },
          data: {
            status: account.error ? 'failed' : 'published',
            platformPostId: account.platformPostId,
            error: account.error ?? null,
            publishedAt: account.publishedAt,
          },
        });
      }
      break;

    case 'post.error':
      for (const account of event.data.socialAccounts) {
        await db.postAccount.update({
          where: { outstandAccountId: account.id },
          data: { status: 'failed', error: account.error },
        });
      }
      break;
  }

  res.status(200).send('OK');
});

Option B: Polling

If you can't use webhooks, poll the post status endpoint:

async function checkPostStatus(outstandPostId: string) {
  const res = await fetch(
    `https://api.outstand.so/v1/posts/${outstandPostId}`,
    { headers: { Authorization: `Bearer ${API_KEY}` } }
  );
  const { post } = await res.json();

  for (const account of post.socialAccounts) {
    await db.postAccount.update({
      where: { outstandAccountId: account.id },
      data: {
        status: account.status,
        platformPostId: account.platformPostId,
        error: account.error,
        publishedAt: account.publishedAt,
      },
    });
  }
}

Complete Integration Flow

Best Practices

  1. Always store Outstand IDs: Save socialNetworkId, socialAccountId, and postId in your database. These are stable identifiers you'll need for all subsequent API calls.

  2. Map accounts to your users: Maintain a relationship between your user records and their connected social accounts. This lets you show users which accounts they have connected and target posts to specific accounts.

  3. Use webhooks over polling: Webhooks give you real-time updates without the overhead of repeated API calls. See the webhooks documentation for setup.

  4. Store platform post IDs: The platformPostId returned after publishing is the native ID on the social platform (e.g., the tweet ID on X). Store this for analytics, deep linking, or comment management.

  5. Handle partial failures: A post can succeed on some accounts and fail on others. Always check the per-account status rather than assuming all-or-nothing. See Post Lifecycle for details.

  6. Index Outstand IDs: Add database indexes on columns storing Outstand IDs (outstand_network_id, outstand_account_id, outstand_post_id) for fast lookups when processing webhooks.