zhyper docs

Queues

A queue is a weekly pattern of publishing times. Instead of picking a timestamp for every post, you describe the rhythm once — "Monday and Wednesday at 09:30, Friday at 16:00" — and then hand posts to the queue. Each post takes the next free slot.

Queues belong to a profile and are resolved in that queue's timezone, so "09:30" survives daylight-saving changes without you recomputing anything.

Like posts, queues read with posts:read and write with posts:write. There is no separate queue permission: a key that can schedule a post can shape the schedule it goes into, and one that cannot, cannot.

Creating one

export async function createWeekdayQueue(
  zhyper: ZhyperClient,
  profileId: string,
): Promise<Schemas['QueueDto']> {
  return zhyper.request<Schemas['QueueDto']>('createQueue', {
    body: {
      profileId,
      name: 'Editorial',
      // `dayOfWeek` 0 = Pazar ... 6 = Cumartesi. `time` yerel duvar saatidir
      // ("HH:MM", 24 saat) ve kuyrugun saat dilimine gore cozulur.
      slots: [
        { id: 'mon-morning', dayOfWeek: 1, time: '09:30' },
        { id: 'wed-morning', dayOfWeek: 3, time: '09:30' },
        { id: 'fri-afternoon', dayOfWeek: 5, time: '16:00' },
      ],
      // Verilmezse profilin saat dilimi devralinir.
      timezone: 'Europe/Istanbul',
    },
  });
}
{
  "id": "b2c3d4e5f6a7b8c9d0e1f2a3",
  "profileId": "a1b2c3d4e5f6a7b8c9d0e1f2",
  "name": "Editorial",
  "timezone": "Europe/Istanbul",
  "slots": [
    { "id": "mon-morning", "dayOfWeek": 1, "time": "09:30" },
    { "id": "wed-morning", "dayOfWeek": 3, "time": "09:30" },
    { "id": "fri-afternoon", "dayOfWeek": 5, "time": "16:00" }
  ]
}

A slot carries exactly three fields and nothing else: id (your own label, no whitespace), dayOfWeek (0 = Sunday through 6 = Saturday) and time ("HH:MM", 24-hour). At least one slot is required. Two slots may not share an id (400 duplicate_slot_id), and two may not land on the same weekday and time (400 duplicate_slot_time) — a duplicated slot would silently double that day's throughput.

A queue carries at most 1008 slots, which is every ten minutes of every day of the week (6 × 24 × 7). A rhythm tighter than that is not really a queue — it is a stream of individually timed posts, and scheduledFor on Posts is the tool for it. Sending more is 400 too_many_slots; the extra slots are never silently dropped, because a truncated pattern would publish on a schedule you did not ask for and the difference would only surface weeks later.

A profile holds at most 100 queues — the same number as one full page of the queue list. The 101st is 400 too_many_queues; delete a queue first. If a profile somehow holds more (queues created before this limit existed), the unpaginated all=true schedule read answers 400 too_many_queues too, rather than quietly returning the first hundred; read those through listQueues, which is cursor-paginated. Deleting is never blocked by the limit.

timezone is optional and defaults to the owning profile's zone. Names are unique per profile: a second "Editorial" is 409 queue_name_taken.

Listing

export async function listQueues(
  zhyper: ZhyperClient,
  profileId: string,
): Promise<Array<Schemas['QueueDto']>> {
  const queues: Array<Schemas['QueueDto']> = [];

  for await (const queue of zhyper.paginate<Schemas['QueueDto']>('listQueues', {
    query: { profileId, limit: 50 },
  })) {
    queues.push(queue);
  }

  return queues;
}

listQueues is cursor-paginated like every other list (pagination) and filters by profileId. sort accepts created_at or -created_at, newest first by default; limit runs 1..100 and defaults to 25.

Seeing where a post would land

export async function previewNextSlots(
  zhyper: ZhyperClient,
  input: { profileId: string; queueId: string; count: number },
): Promise<string[]> {
  const preview = await zhyper.request<
    Schemas['QueueSchedulePreviewResponseDto']
  >('previewQueue', {
    query: {
      profileId: input.profileId,
      queueId: input.queueId,
      count: input.count,
    },
  });

  // Onizleme hicbir sey AYIRMAZ: donen anlar, bugun bos olan slotlardir.
  // Iki musteri ayni onizlemeyi okuyup ayni anda kuyruga atarsa ikincisi
  // bir SONRAKI slota duser.
  return preview.slots ?? [];
}

previewQueue projects the next count slot times (1..100, default 20) as absolute instants. With the queue above, a Wednesday-morning read returns:

{
  "queueId": "b2c3d4e5f6a7b8c9d0e1f2a3",
  "queueName": "Editorial",
  "count": 3,
  "slots": [
    "2026-08-12T06:30:00.000Z",
    "2026-08-14T13:00:00.000Z",
    "2026-08-17T06:30:00.000Z"
  ]
}

Those are UTC instants for 09:30 and 16:00 Istanbul time. The preview reserves nothing. It is a projection of the pattern, not a hold on the calendar, so two clients that read the same preview and then both enqueue will not collide — the second one simply lands on the following slot.

Enqueuing a post

export async function queuePost(
  zhyper: ZhyperClient,
  input: { profileId: string; queueId: string; content: string; targets: PostTarget[] },
): Promise<Schemas['CreatePostResponseDto']> {
  return zhyper.request<Schemas['CreatePostResponseDto']>('createPost', {
    body: {
      content: input.content,
      platforms: input.targets,
      // Kuyruk kipini SECEN alan budur. `queueId` yalnizca profilde tek bir
      // kuyruk varsa atlanabilir; birden fazlaysa acikca secmek gerekir.
      queuedFromProfile: input.profileId,
      queueId: input.queueId,
    },
  });
}

queuedFromProfile is what selects queue mode; queueId picks which queue, and may be omitted only when the profile has exactly one. Sending queueId without queuedFromProfile is 400 queue_id_requires_queue, and combining queue mode with scheduledFor or publishNow is 400 conflicting_schedule_mode (Posts).

The created post comes back with scheduleKind: "recurring", the queueId it drew from, and the queueSlot it was given. Allocation is real, and the next read shows it:

export async function nextFreeSlot(
  zhyper: ZhyperClient,
  input: { profileId: string; queueId: string },
): Promise<string | undefined> {
  const next = await zhyper.request<Schemas['QueueScheduleNextSlotResponseDto']>(
    'getNextQueueSlot',
    { query: { profileId: input.profileId, queueId: input.queueId } },
  );

  return next.nextSlot;
}

Against the same queue, after the Wednesday slot was taken by the post above, getNextQueueSlot answers 2026-08-14T13:00:00.000Z — the Friday slot. Preview does not move; the next-slot pointer does.

An inactive queue answers 400 queue_inactive rather than quietly returning an empty projection.

The /v1/queue compatibility surface

There are two families of endpoint here and it is worth knowing which you are looking at:

/v1/queues/v1/queue/slots
ShapeQueue objects, cursor-paginatedOne profile's schedule
Slot fieldsid, dayOfWeek, timedayOfWeek, time
Extrasactive, isDefault, _id
Built forNew integrationsZernio compatibility

GET /v1/queue/slots requires profileId and returns two different shapes: all=true gives { queues, count } for the whole profile, and anything else gives { exists, schedule, nextSlots } for a single queue. Combining all=true with queueId is rejected (400 invalid_query) rather than silently preferring one. The all=true shape has no pagination, so it is bounded by the 100-queues-per-profile limit above and answers 400 too_many_queues for a profile that exceeds it. Its mutations additionally report reshuffledCount and skippedDailyLimit, which tell you how many already-queued posts moved when the pattern changed.

Prefer /v1/queues for anything new.

Deleting

deleteQueue refuses while an active scheduled post still points at the queue: 409 queue_in_use. Reschedule or cancel those posts first — see cancelPendingTargets in Posts.

Errors worth branching on

CodeStatusMeaning
invalid_slots400slots missing or empty
invalid_slot_day_of_week400Not an integer 0..6
invalid_slot_time400Not HH:MM
duplicate_slot_id400Two slots share an id
duplicate_slot_time400Two slots share a weekday and time
too_many_slots400More than 1008 slots in one queue
too_many_queues400The profile is at (or over) 100 queues
queue_inactive400The queue exists but is switched off
profile_not_found404No such profile for this key
queue_not_found404No such queue for this key
queue_name_taken409Name already used on that profile
queue_in_use409An active scheduled post still points at it

Next

  • Posts — the other three ways to schedule
  • Accounts — the targets a queued post publishes to

On this page