PinFlow
v1 ← Back to app

API Reference

PinFlow exposes a REST API that its own web interface uses. Call these endpoints directly to build integrations, automation scripts, or external dashboards. All requests and responses use JSON.

Quick start

1
Log in at /auth/login Complete the Pinterest OAuth flow in your browser. You'll land on the PinFlow dashboard.
2
Create an API key Open the sidebar → click the ⚙ gear icon → go to the API Keys tab → enter a name and click Generate Key. Copy the sk_pinflow_… secret — it is shown only once.
3
Make your first request
curl https://pinflow.mirajpatterns.com/auth/me \
  -H "Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY"

Authentication

PinFlow uses API key authentication for programmatic access. Every API request must carry your secret key in the Authorization header. Session cookies (set by the browser OAuth flow) are also accepted as a fallback for browser-based usage.

API key format: Each key pair has a public display ID (pk_pinflow_…) and a secret key (sk_pinflow_…). Only the secret key is used for authentication. It is shown once at creation and never retrievable again — store it in a password manager or secret vault.

Key management

Create, disable, and revoke API keys from the dashboard: click the ⚙ gear icon in the sidebar → API Keys tab. You can have up to 10 active keys per account.

Account approval required. Your account must be approved by an admin before API keys work. If you receive 403 Account pending approval, contact the admin.
GET /auth/login Start Pinterest OAuth flow
Request
Response
GET /auth/login HTTP/1.1
Host: pinflow.mirajpatterns.com

No body required. Redirects browser to Pinterest's OAuth consent page.

HTTP/1.1 302 Found
Location: https://www.pinterest.com/oauth/?client_id=...&scope=pins:write+boards:read+user_accounts:read
POST /auth/logout Clear session
Request
Response
POST /auth/logout HTTP/1.1
Host:          pinflow.mirajpatterns.com
Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY
HTTP/1.1 303 See Other
Location: /
GET /auth/me Get authenticated user profile 200
Request
Response
GET /auth/me HTTP/1.1
Host:          pinflow.mirajpatterns.com
Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY
HTTP/1.1 200 OK
Content-Type: application/json

{
  "id":                 1,
  "pinterest_user_id":  "12345678",
  "username":           "your_username",
  "timezone":           "UTC",
  "daily_pin_limit":    2,
  "created_at":         "2026-01-15T10:00:00Z"
}

Required headers

The server checks authentication in this order: API key → session cookie. Use the API key for all programmatic access.

HeaderValueWhen
AuthorizationBearer sk_pinflow_YOUR_SECRET_KEYAll authenticated endpoints — preferred method
X-API-Keysk_pinflow_YOUR_SECRET_KEYAlternative to Authorization header
Content-Typeapplication/jsonPOST / PUT requests with a JSON body
Content-Typemultipart/form-dataPOST /pins/upload-image only

Using both auth methods

# API key (recommended for scripts and automation)
curl https://pinflow.mirajpatterns.com/pins \
  -H "Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY"

# Alternative header name
curl https://pinflow.mirajpatterns.com/pins \
  -H "X-API-Key: sk_pinflow_YOUR_SECRET_KEY"

# Session cookie (browser / fallback — cookie extracted from DevTools)
curl https://pinflow.mirajpatterns.com/pins \
  -b "session=YOUR_SESSION_COOKIE"
401 Unauthorized means your API key is invalid, revoked, or disabled — or no auth header was sent. Create a new key in Settings → API Keys.
403 Forbidden means your account is pending admin approval, or the key's owner was suspended.

Rate limits

Rate limits are per IP address and reset every minute. Exceeding a limit returns 429 Too Many Requests.

EndpointLimit
POST /pins30 / min
POST /pins/bulk10 / min
POST /pins/upload-image30 / min
All other endpoints200 / min (global)
Daily pin limit: Each user account also has a daily cap on how many pins can be created (default: 2). When reached, POST /pins returns 429 with a message indicating when the limit resets (midnight UTC).

Pins

The core resource. A pin is one Pinterest post managed through PinFlow.

Status lifecycle

StatusMeaning
draftSaved but will not publish automatically
reviewFlagged for manual review before queuing
queuedWill publish as soon as the queue processes it
scheduledWill publish at the specified scheduled_at time
publishingCurrently being sent to Pinterest — cannot edit
publishedLive on Pinterest — cannot edit or delete
failedExhausted all retry attempts
GET /pins List pins with optional filters 200
Request
Response

Query parameters

ParamTypeDescription
statusstringFilter by status value
titlestringCase-insensitive partial match
board_idstringExact Pinterest board ID
collection_idintegerOnly pins in this collection
GET /pins?status=scheduled&title=summer HTTP/1.1
Host:   pinflow.mirajpatterns.com
Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY
HTTP/1.1 200 OK
Content-Type: application/json

[
  {
    "id":               42,
    "profile_id":       12,
    "pinterest_user_id": "12345678",
    "pinterest_username": "my_pinterest_profile",
    "board_id":         "123456789",
    "title":            "Summer Recipe Ideas",
    "description":     "Quick and easy summer meals",
    "media_url":        "https://example.com/image.jpg",
    "link":             "https://myblog.com/summer-recipes",
    "alt_text":         null,
    "status":           "scheduled",
    "scheduled_at":     "2026-05-01T19:00:00Z",
    "published_at":     null,
    "pinterest_pin_id": null,
    "error_message":    null,
    "retry_count":      0,
    "sort_order":       0,
    "created_at":       "2026-04-26T12:00:00Z"
  }
]
POST /pins Create a pin  30/min 201
Request
Response

Body fields

FieldTypeNotes
board_idstringrequired — Pinterest board ID from GET /boards
media_urlstringrequired — Image URL, or a URL returned by POST /pins/upload-image
titlestringoptional — Max 256 characters
descriptionstringoptional
linkstringoptional — Destination URL users see when they click the pin
alt_textstringoptional — Max 500 characters
scheduled_atdatetimeoptional — ISO 8601 UTC, must be in the future. Sets status to scheduled.
statusstringoptional — Only "draft" is accepted; omit to queue immediately
POST /pins HTTP/1.1
Host:         pinflow.mirajpatterns.com
Content-Type: application/json
Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY

{
  "board_id":    "123456789012345678",
  "media_url":   "https://example.com/image.jpg",
  "title":       "Summer Recipe Ideas",
  "description": "Quick and easy summer meals",
  "link":        "https://myblog.com/summer-recipes",
  "scheduled_at":"2026-05-01T19:00:00Z"
}
HTTP/1.1 201 Created
Content-Type: application/json

{
  "id":           42,
  "profile_id":   12,
  "pinterest_user_id": "12345678",
  "pinterest_username": "my_pinterest_profile",
  "board_id":     "123456789012345678",
  "title":        "Summer Recipe Ideas",
  "status":       "scheduled",
  "scheduled_at": "2026-05-01T19:00:00Z",
  "created_at":   "2026-04-26T12:00:00Z",
  ...
}

Also fires a pin.created webhook event.

429 if monthly plan quota is reached: { "detail": "Monthly pin limit of 200 reached for plan 'starter'." }

POST /pins/bulk Create multiple pins  10/min 201
Request
Response
POST /pins/bulk HTTP/1.1
Host:         pinflow.mirajpatterns.com
Content-Type: application/json
Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY

[
  { "board_id": "...", "media_url": "...", "title": "Pin 1" },
  { "board_id": "...", "media_url": "...", "title": "Pin 2" }
]

Each item has the same shape as POST /pins. Requires Growth plan or higher, and the entire batch is rejected if it exceeds your monthly plan quota.

HTTP/1.1 201 Created
Content-Type: application/json

[ { "id": 43, ... }, { "id": 44, ... } ]

Each pin object includes profile_id, pinterest_user_id, and pinterest_username.

GET /pins/{id} Get a single pin 200
Request
Response
GET /pins/42 HTTP/1.1
Host:   pinflow.mirajpatterns.com
Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY
HTTP/1.1 200 OK
Content-Type: application/json

{ "id": 42, "profile_id": 12, "pinterest_user_id": "12345678", "pinterest_username": "my_pinterest_profile", "status": "published", ... }
PUT /pins/{id} Update a pin 200
Request
Response

All body fields are optional — send only what you want to change. Pins with status publishing or published cannot be updated. status can be set to draft, review, queued, or scheduled.

PUT /pins/42 HTTP/1.1
Host:         pinflow.mirajpatterns.com
Content-Type: application/json
Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY

{
  "title":        "Updated Title",
  "scheduled_at": "2026-06-01T08:00:00Z"
}
HTTP/1.1 200 OK
Content-Type: application/json

{ "id": 42, "profile_id": 12, "pinterest_user_id": "12345678", "pinterest_username": "my_pinterest_profile", "title": "Updated Title", ... }
DELETE /pins/{id} Delete a pin 204
Request
Response
DELETE /pins/42 HTTP/1.1
Host:   pinflow.mirajpatterns.com
Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY

Pins with status publishing or published cannot be deleted.

HTTP/1.1 204 No Content
POST /pins/{id}/publish Publish a pin immediately 200
Request
Response
POST /pins/42/publish HTTP/1.1
Host:   pinflow.mirajpatterns.com
Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY

No body required. Overrides scheduled_at and pushes the pin to Pinterest right now.

HTTP/1.1 200 OK
Content-Type: application/json

{ "id": 42, "profile_id": 12, "pinterest_user_id": "12345678", "pinterest_username": "my_pinterest_profile", "status": "published", ... }
PUT /pins/reorder Set queue order 200
Request
Response
PUT /pins/reorder HTTP/1.1
Host:         pinflow.mirajpatterns.com
Content-Type: application/json
Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY

{ "pin_ids": [42, 17, 88, 3] }

The array position becomes sort_order. Pins with lower sort_order publish first.

HTTP/1.1 200 OK

{ "updated": 4 }
POST /pins/upload-image Upload an image  30/min 200
Request
Response

Send as multipart/form-data. Field name must be file. Accepted types: JPEG, PNG, GIF, WebP, AVIF. Max: 10 MB.

POST /pins/upload-image HTTP/1.1
Host:         pinflow.mirajpatterns.com
Content-Type: multipart/form-data; boundary=----FormBoundary
Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY

------FormBoundary
Content-Disposition: form-data; name="file"; filename="photo.jpg"
Content-Type: image/jpeg

<binary image data>
------FormBoundary--

# curl shorthand:
curl -X POST https://pinflow.mirajpatterns.com/pins/upload-image \
  -H "Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY" \
  -F "file=@/path/to/photo.jpg"
HTTP/1.1 200 OK
Content-Type: application/json

{
  "url": "https://pinflow.mirajpatterns.com/static/uploads/a1b2c3d4.jpg"
}

Use the returned url as the media_url when creating a pin.

GET /pins/calendar Scheduled pins for a month 200
Request
Response
GET /pins/calendar?year=2026&month=5 HTTP/1.1
Host:   pinflow.mirajpatterns.com
Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY
HTTP/1.1 200 OK

[ { "id": 42, "profile_id": 12, "pinterest_user_id": "12345678", "pinterest_username": "my_pinterest_profile", "scheduled_at": "2026-05-01T19:00:00Z", ... } ]
DELETE /pins/bulk   PUT /pins/bulk/status   PUT /pins/bulk/schedule

DELETE /pins/bulk — delete multiple pins

DELETE /pins/bulk HTTP/1.1
Content-Type: application/json
Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY

{ "pin_ids": [1, 2, 3] }
# Returns 204 · skips published/publishing pins silently

PUT /pins/bulk/status — change status of many pins

PUT /pins/bulk/status HTTP/1.1
Content-Type: application/json
Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY

{ "pin_ids": [1, 2, 3], "status": "draft" }
# Returns { "updated": N } · cannot set to publishing or published

PUT /pins/bulk/schedule — schedule many pins to the same time

PUT /pins/bulk/schedule HTTP/1.1
Content-Type: application/json
Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY

{ "pin_ids": [1, 2, 3], "scheduled_at": "2026-05-01T19:00:00Z" }
# Returns { "updated": N }

Boards

GET /boards List all Pinterest boards 200
Request
Response
GET /boards HTTP/1.1
Host:   pinflow.mirajpatterns.com
Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY

Proxied live from the Pinterest API. Use the id values as board_id when creating pins.

HTTP/1.1 200 OK
Content-Type: application/json

{
  "pinterest_user_id": "12345678",
  "pinterest_username": "my_pinterest_profile",
  "items": [
    {
      "id":          "123456789012345678",
      "name":        "My Travel Board",
      "description": "Places I want to visit"
    }
  ]
}
GET /boards/analytics Get board analytics from your selected Pinterest account 200
Request
Response
GET /boards/analytics HTTP/1.1
Host:          pinflow.mirajpatterns.com
Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY
HTTP/1.1 200 OK
Content-Type: application/json

{
  "pinterest_user_id": "12345678",
  "pinterest_username": "my_pinterest_profile",
  "boards": [
    {
      "board_id": "123456789012345678",
      "total_pins": 42,
      "published_pins": 31,
      "scheduled_pins": 7,
      "draft_pins": 3,
      "failed_pins": 1
    }
  ]
}

Collections

Local groupings of pins — not Pinterest boards. Use them to organise pins by campaign, season, or theme.

GET /collections  POST /collections

GET — list collections

GET /collections HTTP/1.1
Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY

→ 200  [ { "id": 1, "name": "Summer", "pin_count": 5, ... } ]

POST — create a collection

POST /collections HTTP/1.1
Content-Type: application/json
Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY

{ "name": "Summer Campaign" }

→ 201  { "id": 1, "name": "Summer Campaign", "pin_count": 0, ... }
GET /collections/{id}  PUT /collections/{id}  DELETE /collections/{id}

GET — collection detail including pin IDs

GET /collections/1 HTTP/1.1
Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY

→ 200  { "id": 1, "name": "Summer", "pin_count": 3, "pin_ids": [12, 34, 56], ... }

PUT — rename

PUT /collections/1 HTTP/1.1
Content-Type: application/json
Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY

{ "name": "Autumn Campaign" }   → 200

DELETE — remove collection (pins are NOT deleted)

DELETE /collections/1 HTTP/1.1
Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY

→ 204 No Content
POST /collections/{id}/pins  DELETE /collections/{id}/pins/{pin_id}

POST — add a pin (idempotent)

POST /collections/1/pins HTTP/1.1
Content-Type: application/json
Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY

{ "pin_id": 42 }   → 204

DELETE — remove a pin from collection

DELETE /collections/1/pins/42 HTTP/1.1
Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY

→ 204 No Content

Webhooks

Webhooks push real-time event notifications to a URL you control. PinFlow calls your endpoint each time a pin is created, scheduled, published, or fails.

Common integrations: Slack notifications on publish · failure alerts to email · syncing to Google Sheets · triggering n8n / Zapier / Make workflows.

Events

EventFires when
pin.createdA new pin is created in PinFlow
pin.scheduledA pin's scheduled_at is set
pin.publishedA pin goes live on Pinterest
pin.failedA pin exhausts all retries
POST /webhooks Register a webhook 201
Request
Delivery payload
FieldTypeNotes
urlstringrequired — must resolve to a public internet host (no localhost, no private IPs)
eventstringrequired — one of the four event names
secretstringoptional — used to sign deliveries with HMAC-SHA256
POST /webhooks HTTP/1.1
Host:         pinflow.mirajpatterns.com
Content-Type: application/json
Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY

{
  "url":   "https://your-server.com/hooks/pinflow",
  "event": "pin.published",
  "secret":"a-random-string-you-pick"
}

PinFlow sends a POST request to your URL with this shape:

POST /hooks/pinflow HTTP/1.1
Host:                  your-server.com
Content-Type:          application/json
X-Webhook-Signature:   sha256=a1b2c3d4e5f6...

{
  "event":     "pin.published",
  "timestamp": "2026-04-26T14:32:00Z",
  "data": {
    "pin_id":           42,
    "title":            "Summer Recipe Ideas",
    "board_id":         "123456789",
    "status":           "published",
    "pinterest_pin_id": "987654321098765432"
  }
}

Verify the signature

# Python
import hmac, hashlib

def verify(body: bytes, secret: str, header: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(), body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, header)
GET /webhooks  GET /webhooks/{id}  PUT /webhooks/{id}  DELETE /webhooks/{id}
GET    /webhooks       → 200  [ { "id": 1, "profile_id": 12, "pinterest_user_id": "...", "pinterest_username": "...", "url": "...", "event": "pin.published", "is_active": true } ]
GET    /webhooks/1     → 200  { single object }
DELETE /webhooks/1     → 204

# Pause a webhook:
PUT /webhooks/1 HTTP/1.1
Content-Type: application/json
Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY

{ "is_active": false }   → 200

API Keys

Manage the API keys used to authenticate requests. Keys are scoped to the authenticated user — you can only see and manage your own keys.

GET /api-keys List your API keys 200
Request
Response
GET /api-keys HTTP/1.1
Host:          pinflow.mirajpatterns.com
Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY

Returns metadata only — secret keys are never returned after creation.

HTTP/1.1 200 OK
Content-Type: application/json

[
  {
    "id":           1,
    "name":         "n8n integration",
    "display_id":   "pk_pinflow_a1b2c3d4e5f6g7h8",
    "is_active":    true,
    "last_used_at": "2026-04-26T10:00:00Z",
    "created_at":   "2026-04-20T08:00:00Z"
  }
]
POST /api-keys Create a new API key 201
Request
Response
FieldTypeNotes
namestringrequired — a label to identify this key (e.g. "n8n", "Zapier")
POST /api-keys HTTP/1.1
Host:          pinflow.mirajpatterns.com
Content-Type:  application/json
Authorization: Bearer sk_pinflow_YOUR_EXISTING_KEY

{ "name": "n8n integration" }
HTTP/1.1 201 Created
Content-Type: application/json

{
  "id":          2,
  "name":        "n8n integration",
  "display_id":  "pk_pinflow_a1b2c3d4e5f6g7h8",
  "secret_key":  "sk_pinflow_64hexcharactershere...",
  "is_active":   true,
  "created_at":  "2026-04-26T12:00:00Z"
}

The secret_key is only returned here — copy it immediately. It cannot be retrieved again.

PATCH /api-keys/{id}/toggle  DELETE /api-keys/{id}

PATCH /api-keys/{id}/toggle — enable or disable a key

PATCH /api-keys/1/toggle HTTP/1.1
Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY

→ 200  { "id": 1, "is_active": false, ... }

DELETE /api-keys/{id} — permanently revoke a key

DELETE /api-keys/1 HTTP/1.1
Authorization: Bearer sk_pinflow_YOUR_SECRET_KEY

→ 204 No Content

Object schemas

Pin

FieldTypeDescription
idintegerPinFlow ID
user_idintegerOwner's PinFlow ID
board_idstringPinterest board ID
titlestring | null
descriptionstring | null
linkstring | nullDestination URL
alt_textstring | null
media_urlstringImage URL
statusstringSee status lifecycle above
scheduled_atdatetime | nullISO 8601 UTC
published_atdatetime | nullSet on successful publish
pinterest_pin_idstring | nullPinterest's own ID, set after publish
error_messagestring | nullLast error from Pinterest API
retry_countinteger0–3; at 3 → status becomes failed
sort_orderintegerQueue position; lower publishes first
created_atdatetimeISO 8601 UTC

Collection

FieldTypeDescription
idinteger
namestringMax 128 characters
pin_countintegerNumber of pins in this collection
pin_idsinteger[]Only present on GET /collections/{id}
created_atdatetime

Errors

All error responses return JSON. The detail field is either a plain string or an array of validation objects.

StatusMeaning
400Bad request — invalid input (e.g. scheduled_at in the past, private webhook URL)
401Not authenticated — missing, invalid, or revoked API key (or no auth header sent)
403Forbidden — account pending approval, or admin access required
404Not found — resource doesn't exist or belongs to another user
422Validation error — detail is an array (see below)
429Rate limited — IP limit or monthly plan pin quota reached
500Server error
# Plain string error:
{ "detail": "Daily pin limit of 5 reached. Resets at midnight UTC." }

# Validation error array (422):
{
  "detail": [
    {
      "loc":  ["body", "board_id"],
      "msg":  "Field required",
      "type": "missing"
    }
  ]
}