Skip to main content

Signals API

Signals API reference

External API for placing Signals orders programmatically with `sgnl_live_...` bearer keys. JSON in, JSON out, paid from wallet balance.

This spec intentionally covers `/api/external/**` only. Storefront checkout, order-view-token links, mailbox recovery, and credential download routes are customer-account/browser APIs with separate session or signed-link auth.

Mint a key at [/my-account/api-keys](/my-account/api-keys), top up at [/my-account/wallet](/my-account/wallet), then quote → place → poll.

OpenAPI JSON

Authentication

Mint a key at [/my-account/api-keys](/my-account/api-keys). Each customer can have one active key at a time; revoke and recreate to rotate. Keys carry a fixed permission set: `orders:create`, `orders:read`, `catalog:read`, `wallet:read`.

Authorization: Bearer sgnl_live_...
GET/api/external/catalog

List service SKUs

Static service catalog with quantity bounds. Cache aggressively.

Responses

200CatalogResponse

OK

{
  "success": true,
  "services": [
    {
      "sku": "reddit_upvotes",
      "label": "Reddit upvotes",
      "minQuantity": 25,
      "maxQuantity": 1000
    }
  ],
  "accountListings": {
    "url": "/api/external/catalog/listings",
    "note": "Account inventory is dynamic; query listings for availability."
  }
}
401SimpleError

Missing, malformed, or revoked key.

{
  "success": false,
  "errorCode": "UNAUTHORIZED",
  "error": "Invalid API key"
}
403SimpleError

Key lacks the required permission.

{
  "success": false,
  "errorCode": "FORBIDDEN",
  "error": "Missing permission"
}
429SimpleError

Per-key bucket exhausted. Honor the `Retry-After` response header.

{
  "success": false,
  "errorCode": "RATE_LIMITED",
  "error": "Rate limit exceeded"
}
GET/api/external/catalog/listings

List managed-account inventory

Returns the current managed-account inventory. Inventory turns over quickly. Re-query right before placing a managed-account order. Use `listingId` from each entry as the cart item identifier.

Responses

200ListingsResponse

OK

{
  "success": true,
  "accounts": [
    {
      "listingId": "f4c2a1e0-7b8d-4c2f-9e10-3a4b5c6d7e8f",
      "catalogType": "managed_account",
      "name": "u/ex***",
      "totalKarma": 12840,
      "linkKarma": 8200,
      "commentKarma": 4640,
      "country": "United States",
      "age": "4.2 years",
      "ageYears": 4.2,
      "industry": "Technology, Finance",
      "regularPrice": 89,
      "salePrice": 69,
      "goodForNsfw": false,
      "purchaseIncludes": [
        "Reddit account credentials",
        "Linked email with password"
      ]
    }
  ]
}
401SimpleError

Missing, malformed, or revoked key.

{
  "success": false,
  "errorCode": "UNAUTHORIZED",
  "error": "Invalid API key"
}
403SimpleError

Key lacks the required permission.

{
  "success": false,
  "errorCode": "FORBIDDEN",
  "error": "Missing permission"
}
429SimpleError

Per-key bucket exhausted. Honor the `Retry-After` response header.

{
  "success": false,
  "errorCode": "RATE_LIMITED",
  "error": "Rate limit exceeded"
}
POST/api/external/orders/quote

Quote a cart

Re-prices a cart server-side and returns the authoritative subtotal. Always quote before placing; quotes encode line-level pricing rules (extras, delivery speed, selection tier) recomputed on every request. Rate limit: 30/minute.

Request

Schema QuoteRequest

{
  "items": [
    {
      "checkoutMode": "reddit_upvotes",
      "quantity": 50,
      "targetUrl": "https://reddit.com/r/example/comments/abc/post/"
    }
  ]
}

Responses

200QuoteResponse

Quote computed

{
  "success": true,
  "subtotal": 12.5,
  "currency": "USD",
  "expiresAt": "2026-04-27T12:05:00.000Z",
  "lines": [
    {
      "subtotal": 12.5,
      "itemMetadata": {}
    }
  ]
}
400SimpleError

Schema validation failed. The `error` field carries the first failing field message.

{
  "success": false,
  "errorCode": "INVALID_REQUEST",
  "error": "Invalid order request"
}
401SimpleError

Missing, malformed, or revoked key.

{
  "success": false,
  "errorCode": "UNAUTHORIZED",
  "error": "Invalid API key"
}
403SimpleError

Key lacks the required permission.

{
  "success": false,
  "errorCode": "FORBIDDEN",
  "error": "Missing permission"
}
429SimpleError

Per-key bucket exhausted. Honor the `Retry-After` response header.

{
  "success": false,
  "errorCode": "RATE_LIMITED",
  "error": "Rate limit exceeded"
}
GET/api/external/orders

List orders

Lists your orders, newest first. Use `nextCursor` to retrieve the next page. List and detail responses use the same camelCase top-level field style.

Parameters

NameInRequiredDescription
limitquerynoPage size (1–100, default 50).
statusquerynoFilter to a single order status string.
cursorquerynoOpaque cursor returned as `nextCursor`.

Responses

200OrderListResponse

OK

{
  "success": true,
  "orders": [
    {
      "id": 48201,
      "status": "processing",
      "orderType": "reddit_upvotes",
      "subtotal": 12.5,
      "creditApplied": 12.5,
      "total": 0,
      "createdAt": "2026-04-27T11:58:00.000Z"
    }
  ],
  "nextCursor": null
}
401SimpleError

Missing, malformed, or revoked key.

{
  "success": false,
  "errorCode": "UNAUTHORIZED",
  "error": "Invalid API key"
}
403SimpleError

Key lacks the required permission.

{
  "success": false,
  "errorCode": "FORBIDDEN",
  "error": "Missing permission"
}
429SimpleError

Per-key bucket exhausted. Honor the `Retry-After` response header.

{
  "success": false,
  "errorCode": "RATE_LIMITED",
  "error": "Rate limit exceeded"
}
POST/api/external/orders

Place an order

Places an order against wallet balance. The server re-quotes, checks balance, debits the wallet, and stamps the order with `order_source: "api"` and your key's id. A cart with multiple items may produce multiple orders, so `orderIds` is always an array. Rate limit: 30/minute.

Request

Schema OrderRequest

{
  "idempotencyKey": "5b3f48d2-aa1c-4d8b-9c1e-2f8a7b3c4d5e",
  "expectedSubtotal": 12.5,
  "items": [
    {
      "checkoutMode": "reddit_upvotes",
      "quantity": 50,
      "targetUrl": "https://reddit.com/r/example/comments/abc/post/"
    }
  ]
}

Responses

200OrderPlacedResponse

Idempotent replay. The original order was already placed under this `idempotencyKey`; returns the same `orderIds`.

201OrderPlacedResponse

Order placed

{
  "success": true,
  "orderIds": [
    48201
  ],
  "status": "processing",
  "subtotal": 12.5,
  "balanceAfter": 87.5,
  "fulfillment": {
    "kind": "automated",
    "estimatedCompletionMinutes": 30
  }
}
400SimpleError

Schema validation failed. The `error` field carries the first failing field message.

{
  "success": false,
  "errorCode": "INVALID_REQUEST",
  "error": "Invalid order request"
}
401SimpleError

Missing, malformed, or revoked key.

{
  "success": false,
  "errorCode": "UNAUTHORIZED",
  "error": "Invalid API key"
}
402InsufficientBalanceError

Insufficient wallet balance.

{
  "success": false,
  "errorCode": "INSUFFICIENT_BALANCE",
  "error": "Insufficient wallet balance.",
  "balance": 5.25,
  "required": 12.5
}
403SimpleError

Key lacks the required permission.

{
  "success": false,
  "errorCode": "FORBIDDEN",
  "error": "Missing permission"
}
409PriceMismatchError

Server-quoted subtotal differs from `expectedSubtotal`. Re-quote and retry with a fresh `idempotencyKey`.

{
  "success": false,
  "errorCode": "PRICE_MISMATCH",
  "error": "Quoted subtotal does not match expectedSubtotal.",
  "quotedSubtotal": 12.5,
  "expectedSubtotal": 11.99
}
429SimpleError

Per-key bucket exhausted. Honor the `Retry-After` response header.

{
  "success": false,
  "errorCode": "RATE_LIMITED",
  "error": "Rate limit exceeded"
}
500SimpleError

Server-side failure. Safe to retry an order with the same `idempotencyKey`.

{
  "success": false,
  "errorCode": "INTERNAL_ERROR",
  "error": "Could not create checkout attempt"
}
GET/api/external/orders/{orderId}

Get an order

Returns one order, including line items. Returns `404` if the order belongs to another customer.

Parameters

NameInRequiredDescription
orderIdpathyesOrder id from `orderIds[]`.

Responses

200OrderDetailResponse

OK

{
  "success": true,
  "id": 48201,
  "status": "processing",
  "orderType": "reddit_upvotes",
  "createdAt": "2026-04-27T11:58:00.000Z",
  "subtotal": 12.5,
  "creditApplied": 12.5,
  "total": 0,
  "items": [
    {
      "id": 92341,
      "checkout_mode": "reddit_upvotes",
      "quantity": 50,
      "target_url": "https://reddit.com/r/example/comments/abc/post/",
      "subtotal": 12.5
    }
  ]
}
401SimpleError

Missing, malformed, or revoked key.

{
  "success": false,
  "errorCode": "UNAUTHORIZED",
  "error": "Invalid API key"
}
403SimpleError

Key lacks the required permission.

{
  "success": false,
  "errorCode": "FORBIDDEN",
  "error": "Missing permission"
}
404SimpleError

Order not found or owned by another customer.

429SimpleError

Per-key bucket exhausted. Honor the `Retry-After` response header.

{
  "success": false,
  "errorCode": "RATE_LIMITED",
  "error": "Rate limit exceeded"
}
GET/api/external/wallet

Read wallet

Returns wallet balance and the ten most recent credit transactions. Top-ups happen through the storefront; the API is read-only for wallet state.

Responses

200WalletResponse

OK

{
  "success": true,
  "balance": 87.5,
  "currency": "USD",
  "recentTransactions": [
    {
      "id": "1f8e2c40-9a3b-4d1e-b2c1-7e8f9a0b1c2d",
      "type": "order_debit",
      "amount": -12.5,
      "balance_after": 87.5,
      "created_at": "2026-04-27T11:58:00.000Z"
    }
  ]
}
401SimpleError

Missing, malformed, or revoked key.

{
  "success": false,
  "errorCode": "UNAUTHORIZED",
  "error": "Invalid API key"
}
403SimpleError

Key lacks the required permission.

{
  "success": false,
  "errorCode": "FORBIDDEN",
  "error": "Missing permission"
}
429SimpleError

Per-key bucket exhausted. Honor the `Retry-After` response header.

{
  "success": false,
  "errorCode": "RATE_LIMITED",
  "error": "Rate limit exceeded"
}