# Post to social media from Zapier

> Index of all OmniSocials docs pages: https://docs.omnisocials.com/llms.txt

Zapier can create OmniSocials posts with **Webhooks by Zapier**. The Zap sends a `POST` request to the REST API, so any trigger in Zapier can schedule or publish a post: a new row in a sheet, a new blog post, a form submission.

:::caution
Webhooks by Zapier needs a paid Zapier plan. Zapier's pricing page lists webhooks with the paid plans (Professional and up), and the Free plan only runs two-step Zaps. The Zap on this page has three steps, so it needs a paid plan for that reason too.
:::

## Which webhook event to use

Webhooks by Zapier has a **POST** event and a **Custom Request** event. Use **Custom Request** with the method set to `POST`.

The **POST** event builds the body from key and value pairs and turns a list into comma-separated text. OmniSocials needs `accounts` as a real JSON array, so a body from the **POST** event is rejected. **Custom Request** sends the JSON you write, exactly as written.

## Before you start

- An OmniSocials account with at least one social account connected (**Settings → Organisation → Workspaces**). Accounts cannot be connected through the API.
- An API key from **Settings → API** with the `posts:write` scope. Name it "Zapier" so you can find it later. See [Authentication](https://docs.omnisocials.com/authentication.md).
- A Zap with a trigger that provides the post text.

## 1. Prepare the text with Code by Zapier

**Custom Request** does not escape mapped values. A caption with a double quote or a line break would break the JSON. A short code step turns the caption into a safe JSON string first, and works out the publish time.

1. Add a **Code by Zapier** step and choose **Run Javascript**.
2. Under **Input Data**, add a field named `caption` and map the text from your trigger.
3. Paste this code:

```javascript
output = {
  caption_json: JSON.stringify(inputData.caption || ""),
  // Publish one hour from now. Replace with your own time if you have one.
  schedule_at: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
};
```

4. Test the step. `caption_json` comes back as the caption wrapped in quotes, with quotes and line breaks escaped.

If your text never changes, skip this step and type the caption straight into the JSON in the next step.

## 2. Send the request with Webhooks by Zapier

Add a **Webhooks by Zapier** step, choose the **Custom Request** event, and fill in:

| Field | Value |
|-------|-------|
| **Method** | `POST` |
| **URL** | `https://api.omnisocials.com/v1/posts/create` |
| **Data Pass-Through?** | No |
| **Data** | The JSON below |
| **Headers** | `Content-Type`: `application/json`, and `Authorization`: `Bearer omsk_live_<YOUR_KEY>` |

Leave **Basic Auth** empty.

Paste this into **Data**, then replace the two placeholders with the fields from the code step:

```text
{
  "content": { "default": CAPTION_JSON },
  "accounts": ["linkedin", "x"],
  "schedule_at": "SCHEDULE_AT",
  "media_urls": ["https://example.com/cover.jpg"],
  "source": "zapier"
}
```

- Replace `CAPTION_JSON` with the `caption_json` field from the code step. Do not add quotes around it: `JSON.stringify` already added them.
- Replace `SCHEDULE_AT` with the `schedule_at` field from the code step, inside the quotes.

Test the step. A `201` response returns the new post under `data`, with its `id` and a `status` of `scheduled`.

### Where the key lives

Webhooks by Zapier has no credential store. The key is saved in the step's **Headers**, and anyone who can edit the Zap can read it. Keep it narrow:

- Give the key only the scopes this Zap needs, usually just `posts:write`.
- Use a separate key for each Zap, so you can revoke one without breaking the others.
- Revoke the key in **Settings → API** when you delete or hand over the Zap.

:::tip
Zapier also has **API by Zapier**, a beta app with an API Request action that keeps credentials in a connection instead of in the Zap. It also needs a paid plan. See [Send API requests in Zaps](https://help.zapier.com/hc/en-us/articles/44391650357005).
:::

## Body fields

The fields you will use most. The full list, including per-platform options, is in the [API reference](https://docs.omnisocials.com/api) and [Creating posts](https://docs.omnisocials.com/creating-posts.md).

| Field | Type | What it does |
|-------|------|--------------|
| `content` | object | Required. `default` is the text for every account. Add a platform key such as `instagram` or `linkedin` to give that platform its own text. |
| `accounts` | array of strings | Required. Platform names such as `"instagram"`, or account IDs from `GET /v1/accounts` such as `"844008_instagram"`. |
| `schedule_at` | string | ISO 8601 date and time, in the future. The post publishes then. |
| `publish_now` | boolean | `true` publishes right away. It wins over `schedule_at`. |
| `media_urls` | array of URLs | Public image or video URLs. OmniSocials downloads them. Instagram and TikTok posts need at least one. |
| `source` | string | Optional label for where the post came from. |

Leave out both `schedule_at` and `publish_now` to save the post as a draft. Write `publish_now` as `true` without quotes.

## Troubleshooting

| Response | Cause | Fix |
|----------|-------|-----|
| `401` | The key is wrong or revoked, or the `Bearer ` prefix is missing. | Check the `Authorization` header. |
| `403` | The key misses a scope. | Create a new key with the scope. Scopes cannot be edited. |
| `400` with `validation_error` | A field is missing or invalid, for example an unknown account, a past `schedule_at`, or invalid JSON. | Read `error.message`, it names the problem. Invalid JSON usually means a mapped value without the code step. |
| `429` | Rate limit. | Wait for `Retry-After` seconds, then retry. |

See [Rate limits and errors](https://docs.omnisocials.com/rate-limits-and-errors.md) for every status code.

## Next steps

- [Creating posts](https://docs.omnisocials.com/creating-posts.md) covers per-platform text, post types, and updates.
- [Media](https://docs.omnisocials.com/media.md) covers uploads, formats, and size limits.
- [Webhooks](https://docs.omnisocials.com/webhooks.md) can start a Zap when a post publishes or fails.
