Skip to content
Home / Docs / Python SDK

Python SDK

A typed Python client for the PinBridge API, with a sync and an async client. This page covers install, client setup, errors and the common recipes. Every method is listed in the SDK README.

Install

pip install pinbridge-sdk

The package is pinbridge-sdk and the import is pinbridge_sdk. It needs Python 3.10 or later. This page is checked against SDK 1.10.5.

Set up a client

The SDK does not read environment variables. Pass your API key explicitly (the class is PinbridgeClient, with a lowercase “b”):

from pinbridge_sdk import PinbridgeClient

with PinbridgeClient(api_key="$PINBRIDGE_API_KEY") as client:
    accounts = client.pinterest.list_accounts()
    print(accounts)

base_url defaults to https://api.pinbridge.io. The key is sent in the x-api-key header. For a user session, pass bearer_token= instead. See Authentication for how to create a key.

Async client

Use AsyncPinbridgeClient in asyncio code. It has the same resources and methods; you await them:

import asyncio
from pinbridge_sdk import AsyncPinbridgeClient

async def main():
    async with AsyncPinbridgeClient(api_key="$PINBRIDGE_API_KEY") as client:
        pins = await client.pins.list(limit=10)
        print(pins)

asyncio.run(main())

Errors

Errors live in pinbridge_sdk.errors and are also importable from pinbridge_sdk:

  • PinbridgeError is the base class.
  • APIError is raised for any non-2xx response. It carries status_code, message, code, details and response.
  • AuthenticationError (401 and 403), NotFoundError (404), ValidationError (422) and RateLimitError (429) are subclasses of APIError.
from pinbridge_sdk import PinbridgeClient
from pinbridge_sdk.errors import NotFoundError, RateLimitError

with PinbridgeClient(api_key="$PINBRIDGE_API_KEY") as client:
    try:
        pin = client.pins.get("00000000-0000-0000-0000-000000000000")
    except NotFoundError:
        print("no such pin")
    except RateLimitError as e:
        print("slow down:", e.status_code)

code is the stable error code from the API, such as quota_exceeded or pin_already_published. The error reference lists them.

Publish a pin

idempotency_key is required on PinCreate, and you set exactly one of image_url or asset_id. alt_text (up to 500 characters) describes the image for people using screen readers:

from pinbridge_sdk import PinbridgeClient
from pinbridge_sdk.models import PinCreate

with PinbridgeClient(api_key="$PINBRIDGE_API_KEY") as client:
    pin = client.pins.create(
        PinCreate(
            account_id="6f1c2e4a-3b7d-4c9e-8a21-5d0f9b3e7c41",
            board_id="987654321098765432",
            title="Summer styles 2026",
            image_url="https://cdn.shop/img/1.jpg",
            alt_text="Three linen summer dresses on a clothes rail",
            idempotency_key="launch-123",
        )
    )
    print(pin.status)  # queued

Pass the same PinCreate to client.pins.validate(...) for a dry run. It checks the board, the media and your quota without publishing, and returns valid, the checks it ran and the resolved values.

Upload an asset, then publish by asset_id

Upload an image or video to PinBridge storage, then use the returned asset’s id. Asset uploads aren’t available on the Free (Playground) plan, which publishes from a public image_url.

from pinbridge_sdk import PinbridgeClient
from pinbridge_sdk.models import PinCreate

with PinbridgeClient(api_key="$PINBRIDGE_API_KEY") as client:
    with open("pin.jpg", "rb") as f:
        asset = client.assets.upload_image(f)

    pin = client.pins.create(
        PinCreate(
            account_id="6f1c2e4a-3b7d-4c9e-8a21-5d0f9b3e7c41",
            board_id="987654321098765432",
            title="Summer styles 2026",
            asset_id=asset.id,
            idempotency_key="launch-124",
        )
    )
    print(pin.status)

Use client.assets.upload_video(f) for videos.

Publish several pins in one call

client.pins.create_batch takes a list of pins and reports each one separately as created, existing (its idempotency key was already used) or failed. It needs a plan with bulk publishing.

with PinbridgeClient(api_key="$PINBRIDGE_API_KEY") as client:
    result = client.pins.create_batch([pin_one, pin_two, pin_three])  # PinCreate objects
    print(result.created_count, result.existing_count, result.failed_count)

For large files, client.pins.import_csv(f) and client.pins.import_json(rows) start an import job. Follow it with client.pins.get_import(job_id).

Schedule a pin

ScheduleCreate.run_at must be a future time with a timezone. The SDK refuses a time without one before sending anything. Schedules take alt_text too:

from datetime import datetime, timedelta, timezone
from pinbridge_sdk import PinbridgeClient
from pinbridge_sdk.models import ScheduleCreate

with PinbridgeClient(api_key="$PINBRIDGE_API_KEY") as client:
    schedule = client.schedules.create(
        ScheduleCreate(
            account_id="6f1c2e4a-3b7d-4c9e-8a21-5d0f9b3e7c41",
            board_id="987654321098765432",
            title="Autumn drop",
            image_url="https://cdn.shop/img/2.jpg",
            alt_text="A wool coat and scarf laid out on a wooden floor",
            run_at=datetime.now(timezone.utc) + timedelta(hours=2),
        )
    )
    print(schedule.status)  # scheduled

Change, cancel or retry a schedule

Edit a pending schedule in place instead of cancelling it and creating a new one. Send only the fields you change; None clears an optional field such as alt_text:

from pinbridge_sdk.models import ScheduleUpdate

with PinbridgeClient(api_key="$PINBRIDGE_API_KEY") as client:
    client.schedules.update(
        "2d8c7a51-0b9e-4f3a-8c6d-1e2f3a4b5c6d",
        ScheduleUpdate(
            run_at=datetime(2026, 12, 1, 9, 0, tzinfo=timezone.utc),
            alt_text="A wool coat and red scarf on a wooden floor",
        ),
    )

client.schedules.cancel(schedule_id) stops a pending schedule, and client.schedules.retry(schedule_id) runs a failed one again. client.schedules.validate(...) is the dry run.

List, search and page through results

list() returns one page as a list. list_page() takes the same filters and returns a Page with items, total and has_more:

with PinbridgeClient(api_key="$PINBRIDGE_API_KEY") as client:
    page = client.pins.list_page(status="failed", q="autumn", limit=50, offset=0)
    print(page.total, page.has_more)
    for pin in page.items:
        print(pin.id, pin.title)

    upcoming = client.schedules.list(sort="run_at_asc", limit=10)
    one = client.pins.get("8a3f6c12-4d5e-4b7a-9c01-2e6f8d4b7a93")

Edit or retry a pin

client.pins.update(pin_id, PinUpdate(...)) changes a pin’s title, description, link, alt text or board before it publishes. A published pin can’t be edited (the API returns 409 pin_already_published), so delete it and publish a corrected one.

client.pins.retry(pin_id) re-queues a failed pin. Bulk versions exist: client.pins.bulk_retry(ids) and client.pins.bulk_delete(ids).

Boards (under client.pinterest)

There is no client.boards. Boards live under client.pinterest:

with PinbridgeClient(api_key="$PINBRIDGE_API_KEY") as client:
    account_id = "6f1c2e4a-3b7d-4c9e-8a21-5d0f9b3e7c41"
    boards = client.pinterest.list_boards(account_id)
    access = client.pinterest.check_board_access("987654321098765432", account_id=account_id)
    print(access.publishable, access.code)

create_board and update_board take a BoardCreateRequest or BoardUpdateRequest. delete_board(board_id, account_id=...) deletes the board on Pinterest with every pin on it, and can’t be undone. list_related_terms(account_id, ["yoga"]) suggests related search terms.

Analytics and the dashboard summary

with PinbridgeClient(api_key="$PINBRIDGE_API_KEY") as client:
    account = client.pinterest.account_analytics(
        "6f1c2e4a-3b7d-4c9e-8a21-5d0f9b3e7c41",
        start_date="2026-10-01",
        end_date="2026-10-28",
    )
    pin_stats = client.pins.analytics("8a3f6c12-4d5e-4b7a-9c01-2e6f8d4b7a93")
    summary = client.dashboard.summary(start="2026-10-01T00:00:00Z", end="2026-11-01T00:00:00Z")

Account and pin analytics return impressions, saves and clicks per day. The dashboard summary covers publishing activity for a time range: pin outcomes, a time series, the current queue, schedules and import jobs.

Plan, quota and rate limits

with PinbridgeClient(api_key="$PINBRIDGE_API_KEY") as client:
    billing = client.billing.status()
    print(billing.plan, billing.calls_used, billing.quota_calls_monthly, billing.quota_reset_at)

    meter = client.rate_meter.get("6f1c2e4a-3b7d-4c9e-8a21-5d0f9b3e7c41")

calls_used counts the pins published this calendar month (UTC). Plan changes and credit packs are handled in the PinBridge dashboard. See Billing & credits and Rate limits & quotas.

Webhooks

Register a webhook to receive pin.published and pin.failed events. The secret must be 16 to 255 characters:

from pinbridge_sdk import PinbridgeClient
from pinbridge_sdk.models import WebhookCreate, WebhookUpdate

with PinbridgeClient(api_key="$PINBRIDGE_API_KEY") as client:
    hook = client.webhooks.create(
        WebhookCreate(
            url="https://example.com/pinbridge",
            secret="a-secret-at-least-16-chars",
            events=["pin.published", "pin.failed"],
        )
    )

    client.webhooks.list()
    client.webhooks.get(hook.id)
    client.webhooks.update(hook.id, WebhookUpdate(url="https://example.com/hook2"))
    client.webhooks.delete(hook.id)

More examples

Runnable examples for each resource are in the python-examples repository.

Next steps

Last updated October 30, 2026 Was this page helpful? Tell us →