Pinterest Visual Research API Tutorial | SandBase

Build a Pinterest visual-research workflow: list a brand's boards, read a board's pins, and widen with search — one SandBase key, no Pinterest login.

Dark cinematic render of Pinterest boards resolving into a board's pins and a search grid, feeding an agent core

If you research visual trends on Pinterest — how a brand curates, what a board collects, what’s surfacing for a theme — you want a repeatable loop: list a handle’s boards, open the ones that matter, and widen the net with keyword search. This Pinterest Visual Research API tutorial wires that loop with three SandBase endpoints so an agent can run it end to end. It builds on the Pinterest public data API hub; read that first for the big picture.

Everything here is public, read-only data. There is no Pinterest login and no SDK — a SandBase API key is still required to authenticate. The endpoint API reference is the source of truth for parameters and the response envelope; the payload field names below come from calls I ran (tested on 2026-09-27, UTC) and are observed-only — not documented guarantees — so confirm them against a live response, since payloads change over time.

Key takeaway

  • Three endpoints form the loop: user-boards → board → search.
  • Each call is POST /v1/api/pinterest/<path> with only that endpoint’s params, one SANDBASE_API_KEY.
  • The envelope can vary: the reference documents outputs[0].data, but in my calls the payload often arrived under a top-level output object with a success flag, a result list, and a cursor — read either shape.
  • Chain by natural inputs: a handle lists boards, each board’s url opens its pins, and a query widens the set.

The workflow at a glance

  1. List boards for a handle with pinterest/user-boards.
  2. Open a board with pinterest/board using a board url from step 1.
  3. Widen with search using pinterest/search on a query to catch pins beyond that brand.

Each endpoint returns the same envelope — an id, a status, and the model. The reference documents the payload under outputs[0].data; in my calls it often arrived under a top-level output object carrying success, a result list, and a cursor. A failed or timeout run carries error. Read either shape (prefer output, fall back to outputs[0].data) and write the status check once.

This tutorial uses the endpoint-specific Model API references and their POST https://api.sandbase.ai/v1/api/pinterest/... paths. The Pinterest catalog cards expose a separate GET /apis/v1/pinterest/... surface — do not substitute the catalog path. Each step below links its exact reference.

SandBase vs. Pinterest’s official platform

Your needUse
Public, read-only visual researchSandBase Pinterest public-data API
Post, save pins, or act as a memberPinterest’s official platform
Private or account-only dataNeither public workflow

SandBase Pinterest API page with the endpoints used in this workflow The Pinterest endpoints on SandBase — user-boards, board, and search drive this loop.

Step 0: one helper for every call

import os
import requests

API = "https://api.sandbase.ai/v1/api"
HEADERS = {
    "Authorization": f"Bearer {os.environ['SANDBASE_API_KEY']}",
    "Content-Type": "application/json",
}

def call(path: str, payload: dict) -> dict:
    resp = requests.post(f"{API}/{path}", headers=HEADERS, json=payload, timeout=70)
    resp.raise_for_status()
    body = resp.json()
    if body.get("status") != "completed":
        error = body.get("error", {})
        raise RuntimeError(error.get("message", f"{path} did not complete"))
    # Envelope can vary: prefer a top-level `output`, else `outputs[0].data`.
    output = body.get("output")
    if output is None and body.get("outputs"):
        output = body["outputs"][0].get("data", {})
    return output or {}

Every step reads named fields defensively with .get().

Step 1: list a handle’s boards

out = call("pinterest/user-boards", {"handle": "nike"})
boards = out.get("boards", []) if out.get("success") else []
print(len(boards), "boards")

In the response I captured (user-boards run id 962cce4c-cfc8-4fb1-8ed4-943109e4dd97, tested on 2026-09-27, UTC), the output carried a success flag, a boards list, and a cursor. Each board carried a name, a url, a pin_count, and a description. Field names move over time — treat these as observed and confirm against a live response.

first = next((b for b in boards if isinstance(b, dict) and b.get("url")), None)
board_url = first.get("url") if first else None

Step 2: open a board’s pins

out = call("pinterest/board", {"url": board_url})
pins = out.get("pins", []) if out.get("success") else []
cursor = out.get("cursor")
print(len(pins), "pins, more:", bool(cursor))

In my run (board run id 74fa4c3d-6e7d-4c0b-9d7a-61787d564a65), the payload carried a success flag, a pins list, and a cursor. To page, resend board with the returned cursor — confirm the exact parameter name against the board API reference.

SandBase API reference for the Pinterest board endpoint The board reference — the source of truth for the url parameter and the response path.

out = call("pinterest/search", {"query": "minimalist kitchen"})
pins = out.get("pins", []) if out.get("success") else []
print(len(pins), "search pins")

The search endpoint takes a query and returns pins with a cursor (search run id 7298c08a-09be-471b-978a-b155a323d9e3 in my run). This widens your set beyond one brand’s boards to whatever is surfacing for a theme. See the search API reference for the exact parameters.

SandBase API reference for the Pinterest search endpoint The search reference — pass a query; page with the returned cursor.

Putting it together

def research(handle: str, query: str):
    boards_out = call("pinterest/user-boards", {"handle": handle})
    boards = boards_out.get("boards", []) if boards_out.get("success") else []

    collected = []
    for b in boards:
        if not isinstance(b, dict) or not b.get("url"):
            continue
        board_out = call("pinterest/board", {"url": b["url"]})
        collected.append({
            "board": b.get("name"),
            "pins": board_out.get("pins", []) if board_out.get("success") else [],
        })

    search_out = call("pinterest/search", {"query": query})
    return {
        "boards": collected,
        "search": search_out.get("pins", []) if search_out.get("success") else [],
    }

Because all three endpoints share the same envelope, the loop stays flat: one status check in call(...), one output.success guard, and one .get() pattern everywhere. Identifiers flow from step to step — a handle yields boards, each board’s url yields pins.

Practical notes

  • Envelope can vary. The reference documents outputs[0].data; in my calls the payload often arrived under a top-level output object with a success flag. Read either shape (prefer output, fall back to outputs[0].data) and branch on status.
  • Board needs a valid url. Take it from a user-boards result rather than constructing it.
  • Page with the cursor. Both board and search return a cursor; resend it for the next page. Confirm the parameter name against the reference.
  • Business fields are observed-only. The reference guarantees the envelope; treat boards, pins, pin_count, and the like as observed and confirm live.
  • Public, read-only data only. No posting, saving, or private/account-only data. Authenticate with a SandBase API key.
  • Be a good client. Retry with backoff on transient errors such as HTTP 429; page with the cursor rather than hammering.

FAQ

Do I need a Pinterest login or SDK? No. You authenticate to SandBase with your SANDBASE_API_KEY. These read endpoints need no Pinterest account or OAuth on your side.

Why does this surface return output instead of outputs? The reference documents the payload under outputs[0].data; in my calls it often arrived under a top-level output object with a success flag, a result list, and a cursor. Read either shape (prefer output, fall back to outputs[0].data), then check success and read the list defensively.

How do I get a board url? From user-boards. Each board in output.boards carries a url you pass straight to pinterest/board.

How do I page through more pins? Both board and search return a cursor in output. Resend the same call with the cursor to fetch the next page. Confirm the exact parameter name against the reference.

Wrap up

Three endpoints, one output envelope, identifiers flowing between steps — that is the whole visual-research loop. For the full endpoint catalog and response details, see the Pinterest public data API hub. When you are ready: