Skip to content

Social Media API

Public social data and its content, streamed in one format#

Look up a public profile, page, post, hashtag or company on nine networks, then collect its content: posts, comments, reactions, followers, ads, interviews or complaints. Run a one-off job or a 5-day subscription that keeps collecting new content; Zembra handles the collection, scheduling and network changes, and returns every network in the same job, target and stream structure.

  • AccessAPI key
  • Platforms7 supported
  • Endpoint GET /social/{network}/{resource}/{slug} POST /social/job/{network}/{resource}/{slug}/{content} GET /social/job/{jobId}
  • CommitmentNone: pay per use, no minimum

At a glance

  • 9 networks: Facebook, Instagram, LinkedIn, Reddit, TikTok, X, Threads, Glassdoor and the BBB
  • Resource details for 50 credits, returned in the response
  • One-off jobs: 75 credits + 3 per item collected
  • 5-day subscriptions: 150 credits + 3 per item, rechecked every 3 to 24 hours
  • Reading results with GET is free; webhooks deliver new items in pages of 100

Why teams choose Zembra for this#

Coverage

Nine networks and 22 resource types, from Instagram users and hashtags to Facebook advertisers, subreddits, LinkedIn companies, TikTok videos, Glassdoor companies and BBB companies.

One structure

Every response has the same three parts: the job or subscription, the target resource, and the stream of content items, with the network's full payload kept in rawData.

Pay for what is collected

The base fee is charged when the job starts and 3 credits per item as items arrive. sizeLimit and dateLimit cap what is collected and what you pay.

Adaptive rechecks

Subscriptions recheck a resource at an interval derived from how often it publishes, never more often than every 3 hours and never less than once a day.

Push or pull

Poll with free GET calls, or receive each batch of new items on your webhook, Slack or email, in pages of up to 100 items.

Fair failures

Rejected calls and errors cost nothing. A resource that does not exist on the network costs a 25-credit minimum, and asking again for a subscription you already have is free.

How it works

From request to data#

  1. 1

    Look up the resource (optional)

    GET /social/{network}/{resource}/{slug} returns the resource's details right away, such as an Instagram user's name, post count and follower counts. 50 credits.

  2. 2

    Start a job or a subscription

    POST /social/job/…/{content} for a one-off collection, or POST /social/subscription/…/{content} to keep collecting for 5 days. The answer is immediate (201) with the job and the target; the base fee is charged.

  3. 3

    Zembra collects the content

    Items are collected in the background until your sizeLimit or dateLimit is reached. Each item collected is charged 3 credits.

  4. 4

    Receive it

    GET the job or subscription for free: 206 while the first collection is still running, 200 once it has finished. Webhooks receive each batch of new items as it is processed.

  5. 5

    Subscriptions keep going

    For 5 days, Zembra rechecks the resource every 3 to 24 hours and pushes only the new items. With allowNewContent, new posts keep coming even after sizeLimit is reached.

Pricing

Pay per use, no commitment#

Pay per use in credits: a base fee when the request is accepted, then 3 credits per item collected. Credits get cheaper as your volume grows: how credit pricing works.

What a call costs#

CallCreditsStarts at
Resource details
Per lookup; +5 with permalink.
50 $0.01
One-off job, base fee
When the job is created; +25 with permalink.
75 $0.015
Subscription, base fee
When the subscription is created; 0 when it already exists (409); +25 with permalink.
150 $0.03
Per item collected
Charged as items are processed, capped by sizeLimit.
3 $0.0006
Resource not found
Minimum charge on a 404 for details, jobs and subscriptions.
25 $0.005
Reading, listing, updating or cancelling
All GET, PATCH and DELETE calls.
0 Free

Examples#

ProjectCreditsPrice
Details of 10 profiles 500 Free on sign-up
100 posts of an Instagram user, one-off 375 Free on sign-up
A 5-day subscription collecting 50 comments 300 Free on sign-up

Prices use the credit tiers of the cost estimator, where you can estimate your own project. The first 10,000 credits are free.

API reference

Requests and parameters#

Base URL https://api.zembra.io, authenticated with Authorization: Bearer <token>. A sandbox at https://sandbox.zembra.io returns realistic dummy data without using credits.

POST /social/job/{network}/{resource}/{slug}/{content}
cURL
  • cURL
  • .Net
  • Go
  • Java
  • Node.js
  • PHP
  • Python
  • Ruby
curl --location 'https://api.zembra.io/social/job/instagram/user/zembra/posts' \
--header 'Authorization: Bearer {token}' \
--header 'Content-Type: application/json' \
--data '{
  "internalId": "",
  "sizeLimit": null,
  "dateLimit": null
}'
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://api.zembra.io/social/job/instagram/user/zembra/posts");
request.Headers.Add("Authorization", "Bearer {token}");
var content = new StringContent("{
  \"internalId\": \"\",
  \"sizeLimit\": null,
  \"dateLimit\": null
}", null, "application/json");
request.Content = content;
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
Console.WriteLine(await response.Content.ReadAsStringAsync());
package main

import (
  "fmt"
  "strings"
  "net/http"
  "io/ioutil"
)

func main() {

  url := "https://api.zembra.io/social/job/instagram/user/zembra/posts"
  method := "POST"

  payload := strings.NewReader(`{
  "internalId": "",
  "sizeLimit": null,
  "dateLimit": null
}`)

  client := &http.Client {
  }
  req, err := http.NewRequest(method, url, payload)

  if err != nil {
    fmt.Println(err)
    return
  }
  req.Header.Add("Authorization", "Bearer {token}")
  req.Header.Add("Content-Type", "application/json")

  res, err := client.Do(req)
  if err != nil {
    fmt.Println(err)
    return
  }
  defer res.Body.Close()

  body, err := ioutil.ReadAll(res.Body)
  if err != nil {
    fmt.Println(err)
    return
  }
  fmt.Println(string(body))
}
OkHttpClient client = new OkHttpClient().newBuilder()
  .build();
MediaType mediaType = MediaType.parse("application/json");
RequestBody body = RequestBody.create(mediaType, "{
  \"internalId\": \"\",
  \"sizeLimit\": null,
  \"dateLimit\": null
}");
Request request = new Request.Builder()
  .url("https://api.zembra.io/social/job/instagram/user/zembra/posts")
  .method("POST", body)
  .addHeader("Authorization", "Bearer {token}")
  .addHeader("Content-Type", "application/json")
  .build();
Response response = client.newCall(request).execute();
var myHeaders = new Headers();
myHeaders.append("Authorization", "Bearer {token}");
myHeaders.append("Content-Type", "application/json");

var raw = JSON.stringify({
  "internalId": "",
  "sizeLimit": null,
  "dateLimit": null
});

var requestOptions = {
  method: 'POST',
  headers: myHeaders,
  body: raw,
  redirect: 'follow'
};

fetch("https://api.zembra.io/social/job/instagram/user/zembra/posts", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));
<?php
$client = new Client();
$headers = [
  'Authorization' => 'Bearer {token}',
  'Content-Type' => 'application/json'
];
$body = '{
  "internalId": "",
  "sizeLimit": null,
  "dateLimit": null
}';
$request = new Request('POST', 'https://api.zembra.io/social/job/instagram/user/zembra/posts', $headers, $body);
$res = $client->sendAsync($request)->wait();
echo $res->getBody();
import requests
import json

url = "https://api.zembra.io/social/job/instagram/user/zembra/posts"

payload = json.dumps({
  "internalId": "",
  "sizeLimit": None,
  "dateLimit": None
})
headers = {
  'Authorization': 'Bearer {token}',
  'Content-Type': 'application/json'
}

response = requests.request("POST", url, headers=headers, data=payload)

print(response.text)
require "uri"
require "json"
require "net/http"

url = URI("https://api.zembra.io/social/job/instagram/user/zembra/posts")

https = Net::HTTP.new(url.host, url.port)
https.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = "Bearer {token}"
request["Content-Type"] = "application/json"
request.body = JSON.dump({
  "internalId": "",
  "sizeLimit": nil,
  "dateLimit": nil
})

response = https.request(request)
puts response.read_body

Response

{
    "message": "Streaming Instagram posts order received for user < zembra >. Please allow a few minutes before requesting data.",
    "data": {
        "job": {
            "jobId": "",
            "network": "instagram",
            "slug": "zembra",
            "resource": "user",
            "content": "posts",
            "internalId": "",
            "requestedAt": "2023-01-01T00:00:00Z",
            "expiresAt": "2023-01-01T00:00:00Z",
            "sizeLimit": 100,
            "dateLimit": null,
            "cost": 150,
            "active": false
        },
        "target": {
            "slug": "zembra",
            "remoteId": "32146585423198958",
            "displayUrl": "https://instagram.com/instagram/profile.jpg",
            "url": "https://instagram.com/zembra/",
            "link": "https://my.zembratech.com/dashboard/instagram/users/MWJmZTc5MDZjMzg5OGI3OGMyNjJiODNjOGJjMWI3Y2Q/posts",
            "network": "instagram",
            "resource": "user",
            "rawData": []
        }
    }
}
{
    "message": "Invalid resource for selected network"
}
{
    "message": "Invalid bearer token"
}
{
    "message": "Cannot process request: balance too low!"
}
{
    "message": "Requested network [Facebook] is not enabled for your account"
}
{
    "message": "Requested post was not found on Instagram."
}

GET /social/{network}/{resource}/{slug}: Get a resource's details#

networkpath, required: bbb, facebook, glassdoor, instagram, linkedin, reddit, threads, tiktok, x
Must be enabled on your account.
resourcepath, required: depends on the network
For example user, hashtag or post on Instagram; page, post, advertiser or keyword on Facebook; company, profile, post or keyword on LinkedIn.
slugpath, required, URL-encoded, up to 2,048 characters
The resource's identifier on the network, for example an Instagram username. Each resource type checks its own format.

POST /social/job/{network}/{resource}/{slug}/{content}: Collect a resource's content once#

contentpath, required: depends on the resource
What to collect, for example posts, followers or follows of an Instagram user, comments or reactions of a post, ads of a Facebook advertiser, interviews of a Glassdoor company, complaints of a BBB company.
sizeLimitinteger, optional, at least 10
The most recent items to collect, and to pay for. Default: no limit.
dateLimitinteger, optional, Unix seconds from 2010-01-01 to 5 minutes ago
Only keep items published after this date.
internalIdstring, optional, 3–255 characters
Your own reference, returned with the job and in webhooks.
status, type, country, media, from, tooptional, Facebook advertiser and keyword ads only
Filter ads: status active or inactive; type credit, employment, housing or issues; a 2-letter country; media image, meme, video or none; a from and to date.
countryoptional, 2-letter code, LinkedIn only
Country context for the LinkedIn request.

GET /social/job/{jobId}: Read a job's content (free)#

limitinteger, optional, ≥ 0
How many items to return. Only paginates what you read; it never changes what is collected.
offsetinteger, optional, ≥ 0
Items to skip.

GET /social/jobs: List your jobs (free)#

limitinteger, optional, 1–100
Default 100.
offsetinteger, optional
Jobs to skip.
sortByoptional: timestamp, internalId, network
Default timestamp, newest first.
sortDirectionoptional: asc, desc
Overrides the default direction.
internalId, slugstring, optional, 3–1,024 characters
Partial match filters.
network, resource, contentoptional
Exact filters.
allboolean, optional
Include deleted jobs.

POST /social/subscription/{network}/{resource}/{slug}/{content}: Collect a resource's content for 5 days#

sizeLimitinteger, optional, at least 10
How many items to collect. Asking for more than an existing subscription starts a new one.
dateLimitinteger, optional, Unix seconds from 2010-01-01 to 5 minutes ago
Only keep items published after this date.
allowNewContentboolean, optional
Keep collecting items published after the subscription started, even past sizeLimit. Default: your account setting.
internalIdstring, optional, 3–255 characters
Your own reference.

GET /social/subscription/{network}/{resource}/{slug}/{content}: Read a subscription's content (free)#

limitinteger, optional, ≥ 0
How many items to return.
offsetinteger, optional, ≥ 1
Items to skip.

GET /social/subscriptions: List your subscriptions (free)#

limit, offset, sortBy, sortDirection, internalId, slug, network, resource, content, alloptional
Same filters as the job list; limit 1–100, default 100.

PATCH /social/subscription/{network}/{resource}/{slug}/{content}: Change a subscription (free)#

internalIdstring, optional, 3–255 characters
A new reference.
allowNewContentboolean, optional
Turn collection of newly published items on or off.

DELETE /social/subscription/{network}/{resource}/{slug}/{content}: Cancel a subscription (free)#

Status codes#

200Details found, or the job or subscription has finished its first collection.
201Job or subscription created; the target's details are in the response.
206Collection still running: the items collected so far.
400Invalid network, resource, content, slug or parameter. Free.
401Invalid bearer token.
402Balance too low to start the request.
403The network is not enabled for your account.
404The resource was not found on the network (25 credits), or the job or subscription does not exist.
409You already have this subscription; it is returned at no cost.
410The job has expired or was deleted.
429Rate limit reached.
503The network is temporarily down. Free.

Data

What you get back#

A job and its stream#

job.jobIdstring (UUID)
The job's ID, used with GET /social/job/{jobId}.
job.network, resource, slug, contentstring
What the job collects.
job.sizeLimit, dateLimit, internalId, permalink, extraParamsas sent
The options the job was created with.
job.requestedAt, expiresAtdatetime (UTC)
A job's data stays readable for 5 days.
job.costinteger
Credits charged so far: base fee plus items collected.
job.activeboolean
False once the job is deleted.
targetobject
The resource: slug, remoteId, url, network, resource, the network-specific fields (for an Instagram user: name, profileUrl, posts, followed, follow) and rawData.
stream[]array
The content items. Fields depend on the content type; an Instagram post has slug, remoteId, type, timestamp, displayUrl, text, likes, comments, url and rawData.
returnedinteger
Number of items in this response.
Example response
{
    "status": "SUCCESS",
    "message": "List of Instagram posts for user < my-brand >.",
    "data": {
        "job": {
            "jobId": "3f2b7c1e-8a4d-4c55-9e0a-6d1f2b3c4d5e",
            "network": "instagram",
            "resource": "user",
            "slug": "my-brand",
            "content": "posts",
            "sizeLimit": 100,
            "dateLimit": null,
            "internalId": "brand-42",
            "permalink": false,
            "extraParams": [],
            "requestedAt": "2026-01-01 08:00:00",
            "expiresAt": "2026-01-06 08:00:00",
            "url": null,
            "cost": 375,
            "active": true
        },
        "target": {
            "slug": "my-brand",
            "remoteId": "1784140000000000",
            "name": "My Brand",
            "profileUrl": "https://...",
            "posts": 812,
            "followed": 25400,
            "follow": 310,
            "rawData": {
                "…": "everything else the network shows"
            },
            "url": "https://www.instagram.com/my-brand",
            "network": "instagram",
            "resource": "user"
        },
        "stream": [
            {
                "slug": "C1a2B3c4D5e",
                "remoteId": "3290000000000000000",
                "type": "image",
                "timestamp": "2025-12-30 17:12:00",
                "displayUrl": "https://...",
                "text": "New collection out today!",
                "likes": 1520,
                "comments": 48,
                "rawData": {
                    "…": "the full post payload"
                },
                "url": "https://www.instagram.com/p/C1a2B3c4D5e/"
            }
        ],
        "returned": 1
    }
}

What you can collect#

Facebook: page posts, post comments and reactions, advertiser and keyword ads. Instagram: user posts, followers and follows, hashtag posts, post comments and reactions. LinkedIn: company posts and people, profile posts, keyword posts, post comments, reactions and shares. Reddit: subreddit, user and keyword posts, post comments, keyword comments, subreddits and users. TikTok: user and hashtag videos, video comments. X and Threads: user posts. Glassdoor: company interviews. BBB: company complaints.

Jobs or subscriptions#

A job collects once and its data stays readable for 5 days. A subscription lasts 5 days and keeps rechecking the resource. There is one subscription per resource and content: asking again returns the existing one with 409 at no cost, unless you ask for a higher sizeLimit or an older dateLimit, which starts a new one.

How often subscriptions recheck#

The interval is the average gap between the resource's 12 most recent items, kept between 3 hours and 24 hours. Content without timestamps, such as followers or Facebook reactions, is rechecked every 24 hours.

Webhooks#

Each batch of new items goes to every webhook on your account, by HTTP POST, Slack or email, with a reference, the type (social-job or social-subscription), your internalId, the job or subscription, the target and the items, in pages of up to 100 items (page, pages).

When your balance runs low#

Before each collection pass Zembra checks that your balance covers the items it is about to collect; if not, collection stops instead of running into debt. Items are charged as they are processed, so GET only returns items you have paid for.

Enabled networks#

Networks are enabled per account. Instagram, LinkedIn and X are enabled once the account has bought credits; a call to a network that is not enabled answers 403.

Batch requests#

POST /batch/social/details, /batch/social/job and /batch/social/subscription take many resources in one call, with the same options per row; one batch call per minute.

Performance

Speed and limits#

Rate limits #

100 requests per minute per account on calls that start work, 1,000 per minute on reads; batch endpoints accept one call per minute.

Collection size #

sizeLimit is at least 10; without it, everything available is collected (and charged).

Date range #

dateLimit is a Unix timestamp between 2010-01-01 and 5 minutes ago.

Retention #

A job's data can be read for 5 days; after that GET answers 410. Subscriptions run for 5 days.

Lists and pages #

Job and subscription lists return at most 100 entries per call; webhooks carry at most 100 items per delivery.

Identifiers #

Slugs up to 2,048 characters; internalId 3–255 characters.

Supported platforms

7 platforms, one format#

Use cases

What teams build with it#

Social listening

Follow what people post about a brand on hashtags, keywords and comments, with new items pushed as they appear.

Competitive intelligence

Track competitors' posts, follower counts and Facebook ads, filtered by status, country or date.

Employer branding

Collect Glassdoor interviews and LinkedIn company posts and people to understand how a company is seen by candidates.

Complaint monitoring

Pull BBB complaints next to reviews to see every channel customers use to complain.

FAQ

Common questions#

Which networks are supported?

Facebook, Instagram, LinkedIn, Reddit, TikTok, X, Threads, Glassdoor and the Better Business Bureau. Instagram, LinkedIn and X are enabled once your account has bought credits. #

What is the difference between a job and a subscription?

A job collects a resource's content once (75 credits + 3 per item). A subscription keeps collecting new content for 5 days, rechecking every 3 to 24 hours (150 credits + 3 per item). #

How do I control the cost?

Set sizeLimit to the number of items you need, or dateLimit to skip older items. You pay the base fee plus 3 credits per item actually collected. #

Do I have to poll for results?

No. Add a webhook and each batch of new items is pushed to you, in pages of up to 100. Polling with GET also works and is free. #

What happens if the profile does not exist?

You get a 404 and a 25-credit minimum charge. Other errors, such as an invalid parameter or a network that is down, cost nothing. #

Can I get more than the network's display fields?

Yes. Every resource and item includes rawData, the full payload as the network returned it. #

How long is the data kept?

A job's data can be read for 5 days after it was requested; a subscription runs for 5 days. #

Start today for free

Create an account and get 10,000 free credits to try the Social Media API and every other Zembra service. No commitment, no minimum.