# Voholabs Studio public API

> The Voholabs Studio public API is a REST API at https://studio.voholabs.com/api/public/v1, described in OpenAPI at /api/public/v1/openapi.json. Send your API key from Settings › Connect Agent in the Authorization header, with no "Bearer" prefix, to manage channels, media, posts, analytics, the brief, skills and your wallet.

Source: https://voholabs.com/docs/api  
Last updated: 5 October 2026

## How do I authenticate?

Every request carries your API key in the `Authorization` header:

```bash
curl https://studio.voholabs.com/api/public/v1/is-connected \
  -H "Authorization: YOUR_API_KEY"
```

Find the key in **Settings › Connect Agent**, under **API Key**. The same key powers your agent connectors and the [CLI](https://voholabs.com/docs/cli). If you are building a product that posts for other Studio users, use an [OAuth app](https://voholabs.com/docs/api/oauth) instead: its `pos_` tokens go in the same header.

A missing or wrong key returns HTTP 401 with "No API Key found", "Invalid API key" or "Invalid OAuth token".

## How do I rotate my API key?

In **Settings › Connect Agent**, under **API Key**, choose **Rotate Key**. The old key stops working at once, including for agents you connected with a connector link, so update every integration with the new key.

## What can the API do?

All paths are relative to `https://studio.voholabs.com/api/public/v1`.

| Area | Endpoint | What it does |
|---|---|---|
| Account | `GET /is-connected` | Checks that your key works. |
| Channels | `GET /integrations` | Lists connected channels. Add `?group=` to list one customer's channels. |
| Channels | `GET /groups` | Lists customers, the labels that group channels per client. |
| Channels | `GET /integration-settings/:id` | A channel's rules, maximum length and settings schema. |
| Channels | `POST /integration-trigger/:id` | Runs a channel helper, such as listing a Discord server's channels. |
| Channels | `GET /social/:integration` | Returns the address that starts connecting a new channel. |
| Channels | `DELETE /integrations/:id` | Removes a channel. |
| Scheduling | `GET /find-slot/:id` | The next free time slot for a channel. |
| Posts | `GET /posts` | Posts between `startDate` and `endDate`, optionally for one `customer`. |
| Posts | `POST /posts` | Creates, schedules or publishes posts. |
| Posts | `PUT /posts/:id/status` | Moves a post between `draft` and `schedule`. |
| Posts | `DELETE /posts/:id` | Deletes a post and the rest of its group. |
| Posts | `DELETE /posts/group/:group` | Deletes every post in a group. |
| Posts | `GET /posts/:id/missing` | Recent content from the network, to match a post Studio lost track of. |
| Posts | `PUT /posts/:id/release-id` | Links a post to its published version on the network. |
| Media | `POST /upload` | Uploads a file as multipart form data, field `file`. |
| Media | `POST /upload-from-url` | Imports a file from a public address. |
| Media | `GET /media` | Lists the media library, with `page` and `search`. |
| Media | `DELETE /media/:id` | Deletes a file from the library. |
| Media | `POST /upload-ticket` | Creates a single-use upload address, valid for 15 minutes, that needs no key. |
| Analytics | `GET /analytics/:integration` | A channel's analytics for a `date` range. Add `fresh=true` to read the network again instead of the cache; the `X-Cached-At` header says when the numbers were read. |
| Analytics | `GET /analytics/post/:postId` | One post's analytics. |
| Notifications | `GET /notifications` | Your workspace's notifications, by `page`. |
| Brief | `GET /brief/schema` | The brief's sections and the documents you can write to. |
| Brief | `GET /brief` | Every document in the brief. |
| Brief | `GET /brief/:category/:key` | One brief document. |
| Brief | `PATCH /brief/:category/:key` | Writes one brief document. |
| Brief | `DELETE /brief/:category/:key` | Deletes a brief document. Add `keepHistory=true` to keep its revisions. |
| Brief | `GET /brief/onboarding` | The guided onboarding's status: whether it is available, open or finished, whether a new run is charged, and the Studio address where you start it. |
| Brief | `POST /brief-upload/:token` | Uploads a brand file to the brief, with a ticket from `POST /upload-ticket` and `"purpose": "brief"`. |
| Skills | `GET /skills` | The skills library catalogue, with optional `tag` and `search`. Returns summaries, not the methods. |
| Skills | `GET /skills/:slug` | One skill in full, with the steps an agent follows. |
| Wallet | `GET /wallet` | Your credit balance, pay-as-you-go state, auto top-up settings and whether upcoming scheduled posts are covered. |
| Wallet | `GET /wallet/prices` | The price list: what costs credits and what is included free. Add `provider` to see one network. |
| Wallet | `GET /wallet/transactions` | Top-ups, charges, refunds and grants, newest first, with `page`, `size` and `type`. |
| Wallet | `POST /wallet/estimate` | What a post would cost on a channel before you schedule it. Send `provider` and `contents`, the post and each reply. Nothing is charged. |

Uploads accept JPEG, PNG, GIF, WebP, AVIF, BMP and TIFF images up to 10 MB, and MP4 video up to 1 GB.

The brief and skills endpoints are open on paid plans, and on the free plan after your first wallet top-up; before that they return HTTP 402. The wallet endpoints are read-only: topping up stays in the app. On a paid plan they answer with `"usesWallet": false`, because paid plans do not use credits.

## Is there an OpenAPI description?

Yes. The API is described in OpenAPI 3.1 at:

```text
https://studio.voholabs.com/api/public/v1/openapi.json
```

It needs no key. Load it into Postman, Insomnia or a code generator, or give it to an AI agent so it can call the API correctly. It covers every endpoint on this page, with request bodies, responses and error shapes. The per-network `settings` for posts are listed in [Channel settings in the API](https://voholabs.com/docs/api/channel-settings).

## How do I schedule a post?

Send `POST /posts` with the channel ID from `GET /integrations`:

```bash
curl https://studio.voholabs.com/api/public/v1/posts \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "schedule",
    "date": "2026-11-02T09:00:00.000Z",
    "shortLink": false,
    "tags": [],
    "posts": [
      {
        "integration": { "id": "YOUR_CHANNEL_ID" },
        "value": [{ "content": "Hello from the Voholabs Studio API", "image": [] }],
        "settings": { "__type": "linkedin" }
      }
    ]
  }'
```

- `type` is `schedule`, `draft`, `now` or `update`.
- `date` is an ISO 8601 time, required even for drafts.
- `value` holds the parts of the post. Extra entries become follow-up parts, such as a thread or comments.
- `image` takes media you uploaded first, as `{ "id": "...", "path": "..." }`.
- `settings` holds the channel's own options. `GET /integration-settings/:id` returns the schema for each channel, and [Channel settings in the API](https://voholabs.com/docs/api/channel-settings) lists the fields for every network.

A post with no text and no media returns "Your post should have at least one character or one image." Text over the channel's limit returns "post is too long, please fix it".

## Are there rate limits?

Creating posts with `POST /posts` is limited per workspace per hour. Over the limit, the API returns HTTP 429; wait and retry. Other endpoints are covered by fair use.

## What happens when an X post needs credits?

On the free plan, X posts are charged when they are scheduled. To check the cost first, send the post's text to `POST /wallet/estimate`. If your balance is short and auto top-up cannot cover it, `POST /posts` returns HTTP 402 with `"wallet": true` and a `url` pointing at the wallet in the Studio app. Top up, then retry. See [Credits and wallet](https://voholabs.com/docs/billing/credits).

## Can Studio call my server when a post is published?

Yes, with a webhook. Add one in **Settings › Webhooks** and Studio sends a `POST` with the published post's details to your address each time a post goes out. See [Webhooks](https://voholabs.com/docs/api/webhooks) for the payload.

## Frequently asked questions

### Is the API free?

Yes. The public API is part of the free plan. Actions that are pay-per-use in the app, such as posting to X on the free plan, are charged the same way through the API.

### Should I send "Bearer" before my key?

Not for the REST API. Send the key on its own, `Authorization: YOUR_API_KEY`. The MCP server is different and expects `Bearer`.

### Is there a quicker way to build the body for POST /posts?

Yes. In Settings › Connect Agent, choose Open Wizard, build the post in the normal composer, and copy the JSON payload it generates.

## Related pages

- [Webhooks](https://voholabs.com/docs/api/webhooks)
- [Channel settings in the API](https://voholabs.com/docs/api/channel-settings)
- [OAuth apps](https://voholabs.com/docs/api/oauth)
- [The Voholabs CLI](https://voholabs.com/docs/cli)
- [The MCP server](https://voholabs.com/docs/agents/mcp)
