Skip to main content

Queue API

Queues provide first-come, first-served waiting room functionality. Consumers enter, wait in line, and are admitted in order.

Get Queue

Retrieve queue details and current status. GET /api/v1/queues/:queueId

Authentication

  • Publishable key required
  • Consumer authentication not required

Path Parameters

ParameterTypeDescription
queueIdstringThe queue ID

Response

interface Queue {
  id: string;
  name: string;
  sequenceId: string;
  capacity: number | null;
  openAt: string | null;
  closeAt: string | null;
  timeZone: string | null;
  status: "PENDING" | "OPEN" | "CLOSED";
  consumerCount?: number;
}

Example

curl -X GET https://consumer.fanfare.io/api/v1/queues/queue_01HXYZ123456789 \
  -H "X-Publishable-Key: pk_live_xxxxxxxxxxxx"
Response:
{
  "id": "queue_01HXYZ123456789",
  "name": "Holiday Sale Queue",
  "sequenceId": "seq_01HXYZ123456789",
  "capacity": 1000,
  "openAt": "2024-12-01T09:00:00Z",
  "closeAt": "2024-12-01T18:00:00Z",
  "timeZone": "America/New_York",
  "status": "OPEN"
}

Error Responses

StatusErrorDescription
404Queue not foundThe queue ID does not exist

Enter Queue

Join a queue and receive initial position. POST /api/v1/queues/:queueId/enter

Authentication

  • Publishable key required
  • Consumer authentication required

Headers

HeaderRequiredDescription
X-FingerprintRecommendedDevice fingerprint for bot mitigation

Path Parameters

ParameterTypeDescription
queueIdstringThe queue ID

Response

interface QueueConsumerState {
  queueId: string;
  consumerId: string;
  status: "QUEUED" | "ADMITTED" | "COMPLETED" | "DENIED" | "LEFT";
  position: number | null;
  enteredAt: string;
  admittedAt: string | null;
  admissionToken: string | null;
  denyReason: string | null;
}

Example

curl -X POST https://consumer.fanfare.io/api/v1/queues/queue_01HXYZ123456789/enter \
  -H "X-Publishable-Key: pk_live_xxxxxxxxxxxx" \
  -H "Authorization: Bearer eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9..." \
  -H "X-Fingerprint: fp_01HXYZ123456789"
Response:
{
  "queueId": "queue_01HXYZ123456789",
  "consumerId": "cons_01HXYZ123456789",
  "status": "QUEUED",
  "position": 42,
  "enteredAt": "2024-12-01T09:05:23Z",
  "admittedAt": null,
  "admissionToken": null,
  "denyReason": null
}

Error Responses

StatusErrorDescription
400Consumer ID is requiredMissing consumer authentication
404Queue not foundThe queue ID does not exist
423Queue is not open yetQueue opens in the future
429Queue is fullQueue is at capacity

Special Headers on Error

HeaderValueDescription
Retry-AfterISO timestamp or secondsWhen to retry (for 423/429)
X-Fingerprint-ErrortrueFingerprint validation failed

Denied Entry Response

If the consumer is denied entry (e.g., order limit exceeded):
{
  "queueId": "queue_01HXYZ123456789",
  "consumerId": "cons_01HXYZ123456789",
  "status": "DENIED",
  "position": null,
  "enteredAt": "2024-12-01T09:05:23Z",
  "admittedAt": null,
  "admissionToken": null,
  "denyReason": "ORDER_LIMIT_EXCEEDED"
}

Leave Queue

Voluntarily leave a queue. POST /api/v1/queues/:queueId/leave

Authentication

  • Publishable key required
  • Consumer authentication required

Path Parameters

ParameterTypeDescription
queueIdstringThe queue ID

Response

interface QueueConsumerState {
  status: "LEFT";
  // ... other fields
}

Example

curl -X POST https://consumer.fanfare.io/api/v1/queues/queue_01HXYZ123456789/leave \
  -H "X-Publishable-Key: pk_live_xxxxxxxxxxxx" \
  -H "Authorization: Bearer eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9..."
Response:
{
  "queueId": "queue_01HXYZ123456789",
  "consumerId": "cons_01HXYZ123456789",
  "status": "LEFT",
  "position": null
}

Error Responses

StatusErrorDescription
400Consumer ID is requiredMissing consumer authentication
404Queue not foundThe queue ID does not exist
404Consumer not in queueConsumer is not currently queued

Get Queue Status

Get the current consumer’s position and status in the queue. GET /api/v1/queues/:queueId/status

Authentication

  • Publishable key required
  • Consumer authentication required

Path Parameters

ParameterTypeDescription
queueIdstringThe queue ID

Response

Returns QueueConsumerState with current position and status.

Example

curl -X GET https://consumer.fanfare.io/api/v1/queues/queue_01HXYZ123456789/status \
  -H "X-Publishable-Key: pk_live_xxxxxxxxxxxx" \
  -H "Authorization: Bearer eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9..."
Response (Queued):
{
  "queueId": "queue_01HXYZ123456789",
  "consumerId": "cons_01HXYZ123456789",
  "status": "QUEUED",
  "position": 15,
  "enteredAt": "2024-12-01T09:05:23Z"
}
Response (Admitted):
{
  "queueId": "queue_01HXYZ123456789",
  "consumerId": "cons_01HXYZ123456789",
  "status": "ADMITTED",
  "position": 0,
  "enteredAt": "2024-12-01T09:05:23Z",
  "admittedAt": "2024-12-01T09:15:00Z",
  "admissionToken": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9..."
}

Admit Consumer (Server-Side)

Manually admit a consumer. Requires secret key. POST /api/v1/queues/:queueId/admit

Authentication

  • Secret key required

Path Parameters

ParameterTypeDescription
queueIdstringThe queue ID

Request Body

interface AdmitRequest {
  consumerId: string;
}

Example

curl -X POST https://consumer.fanfare.io/api/v1/queues/queue_01HXYZ123456789/admit \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"consumerId": "cons_01HXYZ123456789"}'
Response:
{
  "queueId": "queue_01HXYZ123456789",
  "consumerId": "cons_01HXYZ123456789",
  "status": "ADMITTED",
  "admittedAt": "2024-12-01T09:15:00Z",
  "admissionToken": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9..."
}

Complete Admission (Server-Side)

Mark an admission as completed (consumer has finished checkout). POST /api/v1/queues/:queueId/complete

Authentication

  • Secret key required

Path Parameters

ParameterTypeDescription
queueIdstringThe queue ID

Request Body

interface CompleteRequest {
  consumerId: string;
}

Example

curl -X POST https://consumer.fanfare.io/api/v1/queues/queue_01HXYZ123456789/complete \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"consumerId": "cons_01HXYZ123456789"}'
Response:
{
  "queueId": "queue_01HXYZ123456789",
  "consumerId": "cons_01HXYZ123456789",
  "status": "COMPLETED",
  "completedAt": "2024-12-01T09:20:00Z"
}

Deny Consumer (Server-Side)

Deny a consumer from the queue. POST /api/v1/queues/:queueId/deny

Authentication

  • Secret key required

Path Parameters

ParameterTypeDescription
queueIdstringThe queue ID

Request Body

interface DenyRequest {
  consumerId: string;
  reason: string;
}

Deny Reasons

ReasonDescription
ORDER_LIMIT_EXCEEDEDConsumer has exceeded order limits
FRAUD_DETECTEDSuspicious activity detected
ADMIN_ACTIONManual denial by administrator

Example

curl -X POST https://consumer.fanfare.io/api/v1/queues/queue_01HXYZ123456789/deny \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"consumerId": "cons_01HXYZ123456789", "reason": "FRAUD_DETECTED"}'

Validate Admission Token (Server-Side)

Validate that an admission token is valid for a consumer. POST /api/v1/queues/:queueId/validate

Authentication

  • Secret key required

Path Parameters

ParameterTypeDescription
queueIdstringThe queue ID

Request Body

interface ValidateRequest {
  consumerId: string;
  token: string;
}

Response

Returns true if valid, or an error object with details if invalid.

Example

curl -X POST https://consumer.fanfare.io/api/v1/queues/queue_01HXYZ123456789/validate \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"consumerId": "cons_01HXYZ123456789", "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9..."}'
Valid Response:
true
Invalid Response (Device Mismatch):
{
  "valid": false,
  "error": "DEVICE_MISMATCH",
  "message": "This access token can only be used on the device that originally entered the queue.",
  "code": "FP002",
  "support": {
    "message": "If you need to switch devices, please contact support with your confirmation code.",
    "confirmationCode": "ABC123XYZ"
  }
}

Queue Consumer Statuses

StatusDescription
QUEUEDConsumer is waiting in line
ADMITTEDConsumer has been admitted and can proceed
COMPLETEDConsumer has completed their checkout
DENIEDConsumer was denied entry
LEFTConsumer voluntarily left the queue

SDK Usage

React

import { useQueue } from "@fanfare/sdk-react";

function QueuePage({ queueId }: { queueId: string }) {
  const {
    queue,
    status,
    position,
    isAdmitted,
    admissionToken,
    enter,
    leave,
    isLoading,
    error,
  } = useQueue(queueId);

  if (isLoading) return <Loading />;
  if (error) return <Error message={error.message} />;

  if (isAdmitted) {
    return (
      <div>
        <h2>You're in!</h2>
        <p>Use this token to complete your purchase:</p>
        <code>{admissionToken}</code>
        <a href={`/checkout?token=${admissionToken}`}>Continue to Checkout</a>
      </div>
    );
  }

  if (status?.status === "QUEUED") {
    return (
      <div>
        <h2>You're in line</h2>
        <p>Position: {position}</p>
        <button onClick={leave}>Leave Queue</button>
      </div>
    );
  }

  return (
    <div>
      <h2>{queue?.name}</h2>
      <p>Capacity: {queue?.capacity}</p>
      <button onClick={enter}>Join Queue</button>
    </div>
  );
}

Polling for Updates

import { useEffect, useState } from "react";

function useQueuePolling(queueId: string, intervalMs = 5000) {
  const [status, setStatus] = useState(null);

  useEffect(() => {
    const poll = async () => {
      const response = await fetch(`https://consumer.fanfare.io/api/v1/queues/${queueId}/status`, {
        headers: {
          "X-Publishable-Key": publishableKey,
          Authorization: `Bearer ${accessToken}`,
        },
      });
      const data = await response.json();
      setStatus(data);

      // Stop polling if admitted or completed
      if (data.status === "ADMITTED" || data.status === "COMPLETED") {
        return;
      }
    };

    poll();
    const interval = setInterval(poll, intervalMs);
    return () => clearInterval(interval);
  }, [queueId, intervalMs]);

  return status;
}