Skip to content

REST API v1 — Documentation

Fliok Developer API

Build custom integrations with Fliok using our REST API. Send messages and media, read conversations, contacts and templates, answer Instagram messages and comments, schedule Instagram posts, ask for payments with WhatsApp Pay, and receive signed real-time webhooks — all programmatically.

Base URLhttps://fliok.com/api/v1

224
API Endpoints
Bearer
Token Auth, Role-Based Keys
HMAC
Signed Webhooks
Cursor
Paginated Reads
API reference · Overview

Last updated

Quickstart

Five steps from a key to a signed webhook, with cURL you can paste. You can do all of them in a free developer sandbox before you pay for a plan.

  1. Create a key

    On the Pro or Enterprise plan, create a key on the API keys page and copy it: it is shown once. To try everything first without paying, make a developer sandbox (Integrations → API keys, then Create sandbox) and create a key inside it for every step below.

  2. Check the key

    GET /me answers with your workspace and the key’s role and scopes. A 401 names why a key was refused.

    cURL
    curl https://fliok.com/api/v1/me \
      -H "Authorization: Bearer wa_YOUR_API_KEY"
  3. Send a first message

    A text needs the customer to have written to you in the last 24 hours; outside that window, send an approved template (see Sending). The Idempotency-Key makes a retry safe.

    cURL
    curl -X POST https://fliok.com/api/v1/send \
      -H "Authorization: Bearer wa_YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: order-12345-shipped" \
      -d '{
        "to": "+919876543210",
        "type": "text",
        "text": "Hello! Your order #12345 has been shipped."
      }'
    # Successful response from POST /send (200 OK)
    {
      "ok": true,
      "messageId": "4f6c1e2a-91d3-4a77-b0c2-5f1e8d9a3b44"
    }
    
    // The send is queued, not yet delivered. Poll GET /messages/{id}
    // or subscribe to the message.status webhook for the outcome.
  4. Register a webhook

    Give a public https:// address and the events you want. The answer carries the signing secret (whsec_…) once: store it.

    cURL
    curl -X POST https://fliok.com/api/v1/webhooks \
      -H "Authorization: Bearer wa_YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "url": "https://example.com/webhooks/fliok",
        "events": ["message.received", "message.status"]
      }'
    # -> { "id": "<endpoint id>", "url": "…", "events": [ … ], "active": true,
    #      "createdAt": "…", "secret": "whsec_…" }
  5. Receive one

    Ask for a signed webhook.test delivery now, or wait for a customer to write. Check the signature on every delivery before trusting it: verifying signatures.

    cURL
    curl -X POST https://fliok.com/api/v1/webhooks/<endpoint id>/test \
      -H "Authorization: Bearer wa_YOUR_API_KEY"
    # -> { "ok": true, "eventId": "…" }
    
    # Your server then receives a POST with the headers
    #   x-webhook-id, x-webhook-event, x-webhook-timestamp, x-webhook-signature
    # and the signature is "sha256=" + HMAC-SHA256(secret, timestamp + "." + rawBody), hex.

Using an AI app instead of your own code? Claude, ChatGPT, Cursor and other AI apps can work in Fliok through its MCP server, under the same permissions as this API. See the MCP guide.

Base URL and requests

Base URLhttps://fliok.com/api/v1
Current Versionv1stable

Requests and answers are JSON. A request body can be up to 64 KB for ordinary writes and 1 MB for bulk writes; POST /media takes a file of up to 16 MB. Reads return pages with a nextCursor.

Send a User-Agent header that names your app, such as my-app/1.0. fliok.com is behind Cloudflare, which refuses some default library agents before the request reaches the API — Python’s built-in urllib gets “error 1010”. cURL and Python’s requests work as they are; with any other client, set one.

Authentication

The REST API and webhooks are included on the Pro and Enterprise plans. A key on a workspace that is not on Pro or Enterprise answers 402 plan_feature_unavailable. Pro is charged when you subscribe, and you can cancel any time; the plan runs to the end of the period you paid for. To try the API before you pay, use a developer sandbox.

Fliok API uses Bearer Token authentication. Include your API key in the Authorization header for every request. A key belongs to one workspace and carries one role for all of it — admin, agent or viewer, the same as a teammate — and can be revoked or given an expiry date from the dashboard. A rejected key returns 401 with a specific reason: api_key_expired, api_key_revoked or api_key_invalid, so you can tell a lapsed key from a missing one.

Allowed addresses. A key can be limited to IP addresses or CIDR ranges (IPv4 or IPv6). From anywhere else it answers 403 ip_not_allowed. The keys page shows who made each key and where it was last used from; removing a team member revokes the keys they made unless you choose to keep them. An address that keeps presenting keys that don't exist is refused with 429 for a while.

Money stays in the dashboard. No API key can buy, top up, upgrade or change a payment method: plan, wallet and Fliok invoice reads are open to admin keys with billing:read, and every purchase is made by a signed-in person.

# Include in every request header
Authorization: Bearer wa_xxxxxxxxxxxxxxxxxxxxxxxx

Never expose your API key in client-side code or public repositories. Use environment variables and rotate keys if compromised.

Scopes

A key can also be narrowed to scopes — for example messages:send and templates:read only. The role is the ceiling: a key never gets more than its role allows, and a key with no scopes acts with everything its role allows (every key made before scopes existed). Each endpoint names the scope it needs; a key without it gets 403 insufficient_scope with the missing scope in required. …:write, …:manage and …:delete include …:read of the same thing. The one scope a key below admin can be given is webhooks:manage — the “Zapier / Make / Pabbly triggers” preset — and such a key can only subscribe to events it may read (never invoices or quotations) and only change or remove the endpoints it registered itself. GET /me lists a key's scopes.

Every scope a key can be narrowed to, what it covers, and the lowest role whose keys carry it by default. The same scopes decide which tools an AI app sees over MCP.

ScopeWhat it coversLowest role
messages:sendSend messages and upload filesagent
messages:readRead messages and their delivery statusviewer
contacts:readRead contacts, tags and custom fieldsviewer
contacts:writeAdd and change contacts, tags, blocksagent
contacts:deleteErase contacts and their historyadmin
conversations:readRead chats, notes and quick repliesviewer
conversations:writeAssign, resolve and annotate chats; start chatsagent
templates:readRead templatesviewer
templates:writeCreate, delete and sync templatesadmin
broadcasts:readRead broadcasts, segments and recipientsviewer
broadcasts:writeCreate and cancel broadcasts; manage segmentsadmin
webhooks:readList webhook endpoints (addresses hidden) and eventsviewer
webhooks:manageRegister and manage webhook endpointsadmin
crm:readRead leads, tasks and the pipelineviewer
crm:writeAdd and change leads, notes and tasksagent
instagram:readRead Instagram chats, posts, comments and insightsviewer
instagram:writeReply, comment and post on Instagramagent
payments:readRead WhatsApp Pay requestsviewer
payments:writeSend WhatsApp Pay requestsagent
analytics:readRead reports and costsviewer
billing:readRead the plan, wallet and Fliok's invoicesadmin
team:readRead team members (emails for admin keys only)viewer
automations:readRead flows, bots, automations and auto repliesviewer
automations:writeSwitch automations on and off; start flowsadmin
integrations:readRead which integrations are connectedviewer
integrations:manageSync, configure and disconnect integrationsadmin
biz:readRead invoices and bookings of the business modulesviewer
biz:writeCreate and send invoices, record payments, manage bookingsagent
linkedin:readRead LinkedIn posts, events, page insights and ad resultsviewer
linkedin:writeDraft, schedule and publish LinkedIn posts and eventsagent
ads:readRead ads, their results and Lead Ads formsviewer
ads:writeCreate ads (always paused), pause them, manage Lead Adsadmin
ads:spendPublish or resume ads and raise budgets. Spends ad money; never on unless namedadmin
catalog:readRead the product catalogviewer
catalog:writeAdd, change, delete and sync catalog productsadmin

Who you send to

A customer may have no phone number. WhatsApp lets people use a username, and then shares no number for them. Their phone is wa:<user id> instead of digits, and webhooks and contacts also carry userId and username. Answer with either field below. A verification-code template needs a real number: to a customer without one, POST /send answers 422 needs_phone_number.

POST /send: to or recipient

Every send names the customer one of these two ways.

FieldTypeWhat it is
torequiredstringThe customer’s number in E.164, with or without “+”. A number without a country code takes your sending number’s. For a customer WhatsApp shares no number for, pass the phone we report for them ("wa:<user id>") as it is — or leave `to` out and use `recipient`.
recipientstringInstead of `to`: the customer’s WhatsApp user id (the `userId` on webhooks and contacts, like "IN.21483648909127364650"), for a customer WhatsApp shares no phone number for. `to` wins when both are sent.

Returns See Sending for the rest of the body.

Errors

Every error body has an error field. Branch on it, not on the HTTP status or the message, which is written for people and may change. Failures name the cause — outside_24h_window, contact_opted_out, api_key_expired — instead of a generic 400. Both tables are on Errors.

Rate limits

Limits are per-endpoint token buckets scoped to your workspace, and each key may use at most half of any bucket when a workspace has more than one. Every answer carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset; an empty bucket answers 429 with Retry-After in seconds. Every bucket: Rate limits.

Idempotency

Retries are safe on every write. Send an Idempotency-Key header (1–255 characters, remembered for 24 hours) on any POST, PATCH or DELETE — a message, a lead, a webhook, a secret rotation, a payment voided, a booking completed, an edit or a delete. A payment request is also made safe by your own referenceId. A retry of a finished request answers the first response (with Idempotent-Replayed: true), a retry while it is still running gets 409 idempotency_key_in_progress, and the same key with a different request gets 422 idempotency_key_reused. Keys are kept per API key, so two integrations in one workspace can use the same key without meeting. A request that never finished frees its key after two minutes. Only a success is remembered: after a refusal, fix the request and retry with the same key.

Webhooks and signing

Register a URL and Fliok POSTs each event to it as { id, event, createdAt, data }, signed with HMAC-SHA256 over ${timestamp}.${rawBody} using your endpoint secret, in x-webhook-signature. Delivery is at least once, so de-duplicate on the event id. Non-2xx responses are retried with backoff (12 attempts over about 17 hours), and an endpoint that keeps failing is disabled automatically. The event catalogue and a verification example are on Webhooks.

Versioning

The version is in the path: /api/v1 is the current and only version. Every change to it is dated in the API changelog. Always switch on a webhook’s event and ignore ones you do not handle — new events may be added. The whole surface is also an OpenAPI 3.1 document at /api/v1/openapi.json (no key needed).

Resources

One page per group of endpoints, each with its request bodies, examples and rate limits.

Reference: Errors, Rate limits, Webhook events, Developer sandbox, Changelog.

SDKs and code examples

Clients for JavaScript/TypeScript and PHP, each a single file with no dependencies. They send your key, put an Idempotency-Key on every write, wait out a 429’s Retry-After, and verify webhook signatures. Version 0.1.0, not yet on npm or Packagist; download the file and keep it with your code.

import { Fliok } from './fliok.js';

const fliok = new Fliok({ apiKey: process.env.FLIOK_API_KEY });

// Send an approved template. Fliok builds Meta's components from the variables.
const { messageId } = await fliok.messages.sendTemplate('+919876543210', {
  name: 'order_update', language: 'en', variables: ['Yash', 'ORD-12345'],
});

// Handle a webhook (Express). Use the RAW body: re-serialised JSON will not match.
app.post('/webhooks/fliok', express.raw({ type: 'application/json' }), async (req, res) => {
  if (!(await Fliok.verifyWebhook(req.body, req.headers, process.env.FLIOK_WEBHOOK_SECRET))) return res.sendStatus(401);
  const { id, event, data } = JSON.parse(req.body.toString());
  res.sendStatus(200); // then handle `event`; skip an `id` you have already seen.
});

Without an SDK

Use any HTTP client in your language. Here are ready-to-use examples for the most popular languages.

const API_KEY = 'wa_your_api_key';
const BASE = 'https://fliok.com/api/v1';

// Send a text message (requires an open 24h window)
const res = await fetch(BASE + '/send', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer ' + API_KEY,
    'Content-Type': 'application/json',
    'User-Agent': 'my-app/1.0',
    // Safe to retry: the same key never sends twice (24 hours)
    'Idempotency-Key': 'order-12345-shipped',
  },
  body: JSON.stringify({
    to: '+919876543210',
    type: 'text',
    text: 'Hello from Fliok!',
  }),
});
const data = await res.json();
console.log('Message ID:', data.messageId);

Works with any language that supports HTTP requests — Ruby, Go, Java, .NET, Dart, and more.

Ready to Build?

API access is part of the Pro plan, charged when you subscribe, and you create keys from the API keys page.