PixelPort API Reference


The PixelPort REST API gives read access to a studio's galleries, the media inside them, its clients, and its gallery sales. It also publishes the plan catalog and a live service check with no authentication at all.



Authentication


Send an API key as a bearer token on every authenticated request:


curl -H "Authorization: Bearer pk_live_your_key_here" \
  https://www.pixelport.co/api/v1/me

Create keys in the dashboard under Settings → API. API access is included with the Studio ($129/month) plan. A key is shown once at creation; PixelPort keeps only its SHA-256 hash.


A missing or invalid key returns 401 with a WWW-Authenticate: Bearer header:


{
  "error": {
    "code": "unauthorized",
    "message": "The API key is invalid, revoked, or expired.",
    "docs": "https://www.pixelport.co/docs/api"
  }
}

Endpoints


  • GET /api/v1 — API discovery document. Operation ID getApiIndex. No API key required.
  • GET /api/v1/plans — List plans and prices. Operation ID listPlans. No API key required. Query: planId.
  • GET /api/v1/features — List product features and audiences. Operation ID listFeatures. No API key required. Query: featureId, audienceId.
  • GET /api/v1/status — Check service health. Operation ID getServiceStatus. No API key required.
  • GET /api/v1/me — Retrieve the studio behind the API key. Operation ID getAccount. Requires an API key.
  • GET /api/v1/galleries — List galleries. Operation ID listGalleries. Requires an API key. Query: limit, offset, status.
  • GET /api/v1/galleries/{galleryId} — Retrieve a gallery. Operation ID getGallery. Requires an API key.
  • GET /api/v1/galleries/{galleryId}/media — List media in a gallery. Operation ID listGalleryMedia. Requires an API key. Query: limit, offset, type.
  • GET /api/v1/clients — List clients. Operation ID listClients. Requires an API key. Query: limit, offset, search.
  • GET /api/v1/orders — List gallery sales. Operation ID listOrders. Requires an API key. Query: limit, offset, status.

Each operation ID above is unique across the specification, which is what LLM function-calling importers and code generators use to name the generated function.


Function calling


Every operation in the OpenAPI document carries an x-function-schema: its inputs as one JSON Schema object, with path and query parameters merged and no $ref left to resolve. An operation that takes no arguments says so, with an empty properties object, rather than leaving you to guess whether the parameters were merely undocumented.


`https://www.pixelport.co/tools.json` serves those same schemas as finished tool definitions. Each entry carries the schema twice, under parameters and under input_schema, so the file drops into either tool format with no transformation, plus an endpoint object with the method, the URL template, whether a key is needed, and which arguments belong in the path rather than the query string.


curl -s https://www.pixelport.co/tools.json | jq '.tools[] | {name, authRequired: .endpoint.authRequired}'

Finding these documents


https://www.pixelport.co/.well-known/api-catalog answers with an RFC 9727 catalog, in the RFC 9264 linkset format, naming the specification, the tool definitions, the reference documentation, and the status endpoint. Every /api/v1 response also points at it:


Link: <https://www.pixelport.co/.well-known/api-catalog>; rel="api-catalog"

Conventions


  • Money is an integer number of US cents. A totalCents of 4900 is $49.00. There are no floats anywhere in the API.
  • Timestamps are RFC 3339 strings in UTC, for example 2026-08-21T17:04:05.000Z. A field that has no value is null, never an empty string.
  • Pagination uses limit (1–100, default 25) and offset. Every list response carries a pagination object with total and hasMore. Advance a page by adding limit to offset while hasMore is true.
  • Unlimited plan limits are expressed as -1.
  • Deleted records are never returned.

Example: list galleries


curl -H "Authorization: Bearer pk_live_your_key_here" \
  "https://www.pixelport.co/api/v1/galleries?status=active&limit=2"

{
  "data": [
    {
      "id": "cl8x1p2q0000a08l4h1f2b3c",
      "name": "Johnson Wedding",
      "slug": "johnson-wedding",
      "status": "active",
      "visibility": "password_protected",
      "url": "https://sarah-chen.pixelport.co/johnson-wedding",
      "allowDownloads": true,
      "buyAllEnabled": true,
      "buyAllPriceCents": 25000,
      "watermarkEnabled": true,
      "coverImageUrl": "https://cdn.stack0.dev/.../cover.jpg",
      "eventDate": "2026-06-13T00:00:00.000Z",
      "createdAt": "2026-06-15T18:22:41.000Z"
    }
  ],
  "pagination": { "limit": 2, "offset": 0, "total": 37, "hasMore": true }
}

Example: total a studio's paid sales


curl -s -H "Authorization: Bearer pk_live_your_key_here" \
  "https://www.pixelport.co/api/v1/orders?status=paid&limit=100" \
  | jq '[.data[].totalCents] | add / 100'

Example: read the plan catalog without a key


curl -s https://www.pixelport.co/api/v1/plans | jq '.data[] | {id, priceMonthlyCents}'

Pass planId when only one plan matters:


curl -s "https://www.pixelport.co/api/v1/plans?planId=studio" | jq '.data[0].priceMonthlyCents'

Errors


Every error returns the same envelope. Branch on error.code, which is stable; error.message is written for people and may change.


  • 400 `invalid_request` — A query parameter failed validation. error.details names the offending fields.
  • 401 `unauthorized` — The bearer token is missing, malformed, revoked, or unknown.
  • 404 `not_found` — No record with that id belongs to this studio, or the path is not an endpoint.
  • 405 `method_not_allowed` — The API is read-only. Only GET and OPTIONS are accepted; the response carries Allow: GET, OPTIONS.
  • 429 `rate_limited` — Too many requests. Wait for the Retry-After delay.
  • 500 `internal_error` — Something failed on our side. Retry, then contact support with the x-vercel-id header.

Rate limits


At least 120 requests per 60 seconds are accepted. A request that carries a valid API key is counted against that key; every other request, including the endpoints that need no key, is counted against the client address.


Every response carries the quota, so a client can pace itself rather than discovering the ceiling by hitting it:


RateLimit-Policy: "default";q=120;w=60
RateLimit: "default";r=118;t=57
RateLimit-Limit: 120
RateLimit-Remaining: 118
RateLimit-Reset: 57
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 118
X-RateLimit-Reset: 1787500000

RateLimit and RateLimit-Policy are the fields from the IETF RateLimit header-fields draft: q and w are the policy, r and t are what is left of it. RateLimit-Limit, -Remaining, and -Reset carry the same numbers under the draft's earlier names, which is what most HTTP clients read. `RateLimit-Reset` is a delay in seconds; `X-RateLimit-Reset` is Unix seconds. The X- fields are the form this API published first and are kept unchanged.


A throttled request returns 429 with Retry-After in seconds. Because the counter lives in the process that serves the request, a caller spread across several instances gets a higher effective ceiling than the numbers above — treat them as a floor that is always accepted, not a cap.


Rate-limit fields are per caller, so no shared cache stores an API response. The catalog endpoints (/api/v1, /api/v1/plans, /api/v1/features) send Cache-Control: private, max-age=300, which your own client may honour.


Versioning


Every response carries an x-api-version header holding the date the current response shapes were fixed. New optional fields may be added without a version change; treat unknown fields as forward compatibility rather than an error. A breaking change ships under a new path prefix, and /api/v1 keeps working.


CORS


All /api/v1 endpoints send Access-Control-Allow-Origin: * and answer OPTIONS preflights. This is safe because the API authenticates only with a bearer key and never with cookies, so a browser cannot make an authenticated call on a signed-in user's behalf.