Order endpoints

Place orders paid from your wallet, read and poll their status, and approve, revise or answer questions on them through the API.

Orders placed through the API behave exactly like orders placed in the dashboard: the same publishers, the same delivery flow, the same refund and dispute protection. The difference is that payment is taken from your LinkWatcher wallet at the moment of ordering. Authentication and errors are described in the overview.

Wallet

GET /balance

{ "balance": 640.17, "totalAdded": 1200, "totalSpent": 559.83, "totalRefunded": 0, "currency": "USD", "updatedAt": "...", "topUpUrl": "https://app.linkwatcher.io/marketplace/balance" }

Funds are added in the dashboard only (see Payments and balance). The API can never add, withdraw or move funds; it can only spend the balance on orders within the key's caps. Refunds for rejected or cancelled orders go back to the same wallet.

Pricing

For every order: totalPrice = basePrice + platformFee, where platformFee = max(2.5% of basePrice, $2.50). There is no extra handling fee on wallet payments. The response returns the exact pricing breakdown. Coupons and publisher content writing are not available through the API (400 unsupported_in_v1); provide your own article.

POST /orders

Places an order and pays it from the wallet in one atomic step. Either the order is created and paid, or nothing happens (no pending order, no charge).

FieldRequiredNotes
domainIdyesFrom the catalog
publisherIdyesThe offering's publisherId
serviceTypeyesguest_post or link_insertion
targetUrlyesThe page you want linked (http or https)
anchorTextrecommendedUp to 300 characters
articleTitleguest postsUp to 300 characters
articleContentguest posts (or googleDocsLink)Plain text or HTML, up to 1,000,000 characters. Must be within the offering's minWordCount and maxWordCount (HTML tags are ignored when counting).
googleDocsLinkguest posts (or articleContent)A shared Google Docs URL the publisher can open
specialRequirementsnoUp to 5,000 characters, shown to the publisher
externalRefrecommendedYour own order id, up to 128 characters. Reusing it returns the existing order instead of creating a second one, so retries are safe.
curl -X POST https://app.linkwatcher.io/api/v1/orders \
  -H "Authorization: Bearer lw_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "domainId": "3682f290-...",
    "publisherId": "cda5e81e-...",
    "serviceType": "link_insertion",
    "targetUrl": "https://client-site.com/page",
    "anchorText": "best running shoes",
    "specialRequirements": "Please place the link in an existing article about fitness.",
    "externalRef": "gpm-order-10422"
  }'

Responses:

  • 201 with { "order": {...}, "payment": { "balanceUsed": 21.5, "newBalance": 618.67, "currency": "USD" }, "replayed": false }. The order status is paid, an invoice PDF is attached (order.invoiceUrl, it may fill in a few seconds later) and the publisher is notified.
  • 200 with "replayed": true when the externalRef already exists.
  • 402 insufficient_balance with required, available and topUpUrl. Nothing was charged.
  • 403 order_cap_exceeded or 403 daily_cap_exceeded, 422 word_count_out_of_range, 404 offering_unavailable, 409 publisher_unavailable as listed in the overview.

The order object

{
  "id": "f52cc433-...",
  "orderNumber": "MP-20260924-001257",
  "status": "paid",
  "serviceType": "link_insertion",
  "domain": { "id": "e5566d7a-...", "domainName": "theavtub.com", "url": "https://theavtub.com" },
  "publisherId": "e6e1b82b-...",
  "targetUrl": "https://client-site.com/page",
  "anchorText": "best running shoes",
  "articleTitle": null,
  "googleDocsLink": null,
  "specialRequirements": "...",
  "pricing": { "basePrice": 30, "contentWritingFee": 0, "platformFee": 2.5, "couponDiscount": 0, "totalPrice": 32.5, "currency": "USD" },
  "publishedUrl": null,
  "completionNotes": null,
  "rejectionReason": null,
  "refundReason": null,
  "disputeReason": null,
  "deadlineAt": "2026-10-01T09:01:04.954Z",
  "createdAt": "2026-09-24T09:01:04.954Z",
  "paidAt": "2026-09-24T09:01:04.954Z",
  "acceptedAt": null,
  "submittedAt": null,
  "completedAt": null,
  "refundedAt": null,
  "updatedAt": "2026-09-24T09:01:04.954Z",
  "invoiceUrl": "https://.../LWM-MP-20260924-001257.pdf",
  "externalRef": "gpm-order-10422",
  "orderSource": "api",
  "clarification": { "status": null, "count": 0, "openQuestion": null },
  "history": [ { "from": "pending_payment", "status": "paid", "at": "2026-09-24T09:01:04.954Z" } ],
  "clarifications": []
}

history and clarifications are included on single-order responses (POST /orders, GET /orders/{id} and the action endpoints), not in the list.

GET /orders

Your orders, newest first. Filters: status (one) or statusIn (comma separated), externalRef, createdAfter (ISO date), page, limit.

GET /orders/{orderId}

Accepts the order id or the orderNumber (MP-...). Add ?includeContent=true to receive the stored articleContent.

Lifecycle and polling

StatusMeaningYour move
paidPaid, waiting for the publisher to acceptWait
clarification_requestedThe publisher asked a question; the delivery clock is paused. clarification.openQuestion holds it.Answer with POST /orders/{id}/clarification-answer
accepted or in_progressPublisher is working on it. deadlineAt is the promised delivery time.Wait
submittedDelivered: publishedUrl holds the live URLCheck the page, then approve or request a revision within 72 hours
revision_requestedYou sent it back; the publisher is revisingWait for a new submitted
completedApproved (by you or automatically after 72 hours). The publisher is paid.Done
rejectedThe publisher declined (rejectionReason). The money is refundable to your wallet from the dashboard.Handle in the dashboard
refund_requested or refundedRefund flow (dashboard)Nothing
cancelledCancelled by LinkWatcher in your favour; the amount was refunded to your walletNothing
disputedUnder review by LinkWatcher (disputeReason)Wait

Polling guidance: poll GET /orders?statusIn=paid,accepted,in_progress,clarification_requested,submitted,revision_requested every 15 to 60 minutes and treat history[] as the event log. Orders in submitted auto-complete 72 hours after submittedAt, so approve or request a revision before then if you want a say. Webhooks are not available in v1.

POST /orders/{orderId}/approve

submitted to completed. Releases the publisher's earnings. Returns 409 invalid_transition (with currentStatus) if the order is not submitted; treat currentStatus: "completed" as success (auto-completion already happened).

POST /orders/{orderId}/revision

Body { "notes": "The anchor text is wrong, please use ..." }. submitted to revision_requested. Notes are required (up to 5,000 characters) and are shown to the publisher.

POST /orders/{orderId}/clarification-answer

Body { "answer": "..." }. Answers the publisher's open question; the order returns to its previous status and the deadline is extended by the time it waited on you.

Not available through the API (use the dashboard): refund requests, resubmitting a rejected order, reviews, disputes, and publisher-written articles.