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).
| Field | Required | Notes |
|---|---|---|
domainId | yes | From the catalog |
publisherId | yes | The offering's publisherId |
serviceType | yes | guest_post or link_insertion |
targetUrl | yes | The page you want linked (http or https) |
anchorText | recommended | Up to 300 characters |
articleTitle | guest posts | Up to 300 characters |
articleContent | guest 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). |
googleDocsLink | guest posts (or articleContent) | A shared Google Docs URL the publisher can open |
specialRequirements | no | Up to 5,000 characters, shown to the publisher |
externalRef | recommended | Your 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:
201with{ "order": {...}, "payment": { "balanceUsed": 21.5, "newBalance": 618.67, "currency": "USD" }, "replayed": false }. The order status ispaid, an invoice PDF is attached (order.invoiceUrl, it may fill in a few seconds later) and the publisher is notified.200with"replayed": truewhen theexternalRefalready exists.402 insufficient_balancewithrequired,availableandtopUpUrl. Nothing was charged.403 order_cap_exceededor403 daily_cap_exceeded,422 word_count_out_of_range,404 offering_unavailable,409 publisher_unavailableas 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
| Status | Meaning | Your move |
|---|---|---|
paid | Paid, waiting for the publisher to accept | Wait |
clarification_requested | The publisher asked a question; the delivery clock is paused. clarification.openQuestion holds it. | Answer with POST /orders/{id}/clarification-answer |
accepted or in_progress | Publisher is working on it. deadlineAt is the promised delivery time. | Wait |
submitted | Delivered: publishedUrl holds the live URL | Check the page, then approve or request a revision within 72 hours |
revision_requested | You sent it back; the publisher is revising | Wait for a new submitted |
completed | Approved (by you or automatically after 72 hours). The publisher is paid. | Done |
rejected | The publisher declined (rejectionReason). The money is refundable to your wallet from the dashboard. | Handle in the dashboard |
refund_requested or refunded | Refund flow (dashboard) | Nothing |
cancelled | Cancelled by LinkWatcher in your favour; the amount was refunded to your wallet | Nothing |
disputed | Under 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.