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:
PinbridgeErroris the base class.APIErroris raised for any non-2xx response. It carriesstatus_code,message,code,detailsandresponse.AuthenticationError(401 and 403),NotFoundError(404),ValidationError(422) andRateLimitError(429) are subclasses ofAPIError.
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
- Authentication: create the API key the client uses.
- API reference: every endpoint the SDK calls.
- Rate limits & quotas: what counts against your quota.
- MCP setup: the same publishing pipeline from an AI assistant.
