All articles
Platform

Send Many Offers to Vereel from a Spreadsheet Through the API

A smiling woman taking a pair of white sneakers out of a shoebox at a desk, with a camera and a furry microphone beside her

If your campaigns already live in a spreadsheet or a product feed, you do not need to retype them. A short script can read the rows and send each one to Vereel as an offer. The limits of the API shape how to do it well: Vereel reviews every offer, so a batch is a queue, not a flood. This is a script that works with those limits instead of fighting them.

What the limits mean for a batch

Every offer sent through the API waits for a review by a person at Vereel. To keep that workable, a brand can have up to 20 offers in review at once and send up to 50 new offers a day. One token can make 30 changes in 10 minutes. The 21st offer in a row is refused with REVIEW_QUEUE_FULL until some of the first 20 have been reviewed.

So plan batches of up to 20 offers, and send the next batch once the first ones have been approved or returned. A hundred offers is five batches over several days, not one run.

Prepare the sheet

One row per offer. Save it as a CSV file named offers.csv (UTF-8) with these columns:

  • name and description: the text creators see.
  • platforms: instagram, tiktok, youtube or x, separated by semicolons.
  • category: the name of a Vereel category, exactly as GET /categories lists it.
  • product_value and included: what the creator receives, and what it is worth in PLN.
  • image_url: a link to a JPEG, PNG or WebP of up to 5 MB, on a public https address.

The script

The example sends barter offers for a product. Paying creators adds isPayment and additionalPayment to the same request.

Save the script as send_offers.py, put your token in the VEREEL_TOKEN environment variable (never in the file) and run it with Python 3 and the requests library. It sends the rows in order and stops when it should.

import csv
import os
import time

import requests

API = "https://www.vereel.com/api/v1/public"
HEADERS = {"Authorization": f"Bearer {os.environ['VEREEL_TOKEN']}"}

# Vereel's category names, mapped to the ids the API wants.
categories = requests.get(f"{API}/categories", timeout=30).json()["data"]
category_id = {c["name"]: c["id"] for c in categories}


def offer_from(row):
    return {
        "name": row["name"],
        "description": row["description"],
        "offerType": "PRODUCT",
        "platforms": row["platforms"].split(";"),
        "categoryIds": [category_id[row["category"]]],
        "isPayment": False,
        "isBarter": True,
        "productServiceValue": float(row["product_value"]),
        "currency": "PLN",
        "includedDescription": row["included"],
        "imageUrls": [row["image_url"]],
    }


def send(row):
    """Returns "sent", "skipped" or "stop"."""
    for attempt in range(3):
        try:
            res = requests.post(f"{API}/my/offers", json=offer_from(row), headers=HEADERS, timeout=90)
        except requests.RequestException as err:
            # The offer may have been created anyway: look before sending it again.
            print("no answer for", row["name"], err, "- check GET /my/offers first")
            return "stop"
        if res.status_code == 201:
            print("sent", row["name"], res.json()["data"]["id"])
            return "sent"
        try:
            error = res.json()["error"]
        except ValueError:
            print("unexpected answer", res.status_code, "for", row["name"])
            return "stop"
        if error["code"] == "REVIEW_QUEUE_FULL":
            return "stop"
        if error["code"] == "RATE_LIMITED":
            time.sleep(int(res.headers.get("Retry-After", "60")))
            continue
        print("skipped", row["name"], error["code"], error["details"])
        return "skipped"
    return "stop"


with open("offers.csv", newline="", encoding="utf-8") as f:
    for row in csv.DictReader(f):
        if send(row) == "stop":
            print("Stopped at", row["name"], "- run again later with the rows that are left.")
            break

What the script does when something goes wrong

It handles four cases, and each one is a choice you can change:

  • A row the API refuses (422): it prints the error with the name of each wrong field, skips the row and goes on.
  • Too many requests (RATE_LIMITED): it waits as long as the Retry-After header says and tries the same row again, up to three times.
  • A full review queue (REVIEW_QUEUE_FULL): it stops. A few minutes of waiting will not help; the queue empties as people review. Run it again later with the rows that are left.
  • No answer at all (a timeout or a dropped connection): it stops, because the offer may have been created. The API has no idempotency key, so sending the row again could create a duplicate. Look at GET /my/offers first.

Check what happened

Every sent offer is created with the status in_review. GET /my/offers lists them with their status: active once approved, or rejected with the reviewer's note when Vereel returned it to your drafts. A rejected offer is a draft in your campaigns: you can fix it and publish it in the app, or send a corrected one.

Write down the id the script prints for each row. It is the simplest link between a row in your sheet and an offer in Vereel.

Habits that save time

A few small things save hours:

  • Send three rows first, then the whole sheet.
  • Keep the images small and reachable: Vereel downloads each one while the offer is being created, and an image it cannot fetch refuses the whole offer.
  • Use a separate token for the script and name it after the script, so you can revoke it when the job is done.

A spreadsheet can feed Vereel quickly, but the review keeps the pace human. Send in waves, keep a record of the ids, and let the script stop rather than guess.

Frequently asked questions

How many offers can I send at once?
Up to 20 can wait in review at the same time, and you can send up to 50 new API offers in a day. A 21st offer in a row is refused until some have been reviewed.
Can the script publish the offers?
No. Offers sent through the API wait for Vereel's review. They are public only after approval.
What if the script stops in the middle?
Look at GET /my/offers to see which rows were created, remove those rows from the sheet and run the script again. The API has no duplicate protection, so do not resend a row you have not checked.
Do I need Python?
No. The script only shows the steps: read a row, send one request, look at the answer. Any language that can send an HTTP request and read a CSV file can do the same.

Related reading

How to Add an Offer to Vereel Through the APIHow to Keep Your Vereel API Token Safe
Ready to run a better campaign?

Post an offer and match with verified creators on Vereel.

Get started

Vereel for brands