This API is not available yet
Every endpoint documented here returns 404 today.
The Linkyard API
A REST interface over the same pipeline the dashboard uses: bearer auth, idempotent order creation, signed webhooks, no SDK. This page is the design it will be built to.
This API is not available yet
No endpoint in the API reference is live: a request to any of them returns 404 today. What is published is the specification being built to, so an integration can be designed against a stable contract before the routes ship.
Treat everything below as the contract being worked towards, not as something you can call. Nothing on this page is a description of shipped behaviour.
Authentication
Requests will carry your key as a bearer token, and a key will be shown once at creation. Keys are not being issued yet, because there is nothing for one to authenticate against.
Authorization: Bearer ly_live_…
Base URL
Endpoints will be relative to this, on the canonical host — redirects strip the Authorization header. Nothing is served under it yet.
https://seo.nulldesign.co/api/v1
Payment
Orders will draw down your account balance. There is no card on file to charge, which is why balance is the only funding path planned for API orders.
402 insufficient_balance
Your first call
Free, and it will not touch your balance — the natural way to confirm a key works. Run it today and you get an HTML 404 from the framework, not the JSON error envelope documented below, because the route does not exist.
curl https://seo.nulldesign.co/api/v1/account \
-H "Authorization: Bearer ly_live_..."Reference
Endpoints
The ordering, project, marketplace and reporting surfaces of the dashboard — not billing, affiliate or account management. None of it is built yet: every endpoint below is marked accordingly, and every one of them returns 404.
Account
Your balance, usage and limits.
/accountnot implementedfreeRetrieve the authenticated account
Returns email, balance in cents, webhook configuration and current usage against the project and report caps.
curl https://seo.nulldesign.co/api/v1/account \
-H "Authorization: Bearer ly_live_..."Products
The catalog and the intake schema each product expects.
/productsnot implementedfreeList the public catalog
Excludes subscription products and anything with special pricing. Includes volume tiers.
/products/{id}/intake-schemanot implementedfreeGet the intake JSON Schema for a product
Returns a JSON Schema describing exactly what the `intake` object must contain for this product. Validate against it before creating an order.
Marketplace
Publisher inventory and its filter facets.
/marketplace/listingsnot implementedrate limitedList publisher inventory
Paginated. Domains are included for verified accounts. Heavily rate limited because the full inventory is the product.
- typestring
- guest_post | link_insert | quote_link | homepage_link
- min_drinteger
- Lower bound on Domain Rating.
- max_priceinteger
- Upper bound in cents.
- nichestring
- Repeatable. Matches any listed niche.
- pageinteger
- 1-based. 25 per page.
/marketplace/filtersnot implementedfreeList available filter values
Distinct niches, languages, countries and the price and DR bounds currently in inventory.
Orders
Create orders, poll delivery, read the message thread.
/orders/validatenot implementedfreeDry-run an order
Validates intake, prices the order and checks your balance without charging anything or consuming uploads. Always call this first while you are building an integration.
/ordersnot implementedcharges balanceCreate an order
Deducts the total from your balance and queues fulfillment. Pass `idempotency_key` so a retried request cannot create a duplicate.
- items*array
- Each item: product_id, quantity, intake.
- project_idstring
- Attaches the order to a project.
- idempotency_keystring
- Safe retries. Unique per order.
curl -X POST https://seo.nulldesign.co/api/v1/orders \
-H "Authorization: Bearer ly_live_..." \
-H "Content-Type: application/json" \
-d '{
"idempotency_key": "order-2026-08-10-001",
"items": [{
"product_id": "prod_...",
"quantity": 5,
"intake": {
"target_url": "https://example.com/pricing",
"anchor_text": "pricing page",
"keyword": "project management pricing"
}
}]
}'/orders/{id}not implementedfreeRetrieve an order
Status, items, pricing and the intake state of each item.
/orders/{id}/deliverynot implementedfreePoll for deliverables
Live URLs, anchors and verification state. For a managed-budget parent this consolidates progress across every child order.
/orders/{id}/childrennot implementedfreeList child orders
Managed-budget parents only.
/orders/{id}/messagesnot implementedfreeRead the message thread
/orders/{id}/messagesnot implementedfreePost a reply
Notifies the person handling the order by email.
File uploads
Attach files to an order's intake.
/intake-attachmentsnot implementedfreeUpload a file
Max 10 MB. Images, PDF, text, CSV, Word and Excel. The returned id expires after 24 hours and can be used by exactly one order.
Projects
The domains, keywords and brand terms everything is measured against.
/projectsnot implementedfreeCreate a project
Only `name` is required.
/projectsnot implementedfreeList projects
Newest first.
/projects/{id}not implementedfreeRetrieve a project
/projects/{id}not implementedfreeUpdate a project
`urls` and `keywords` replace the whole list rather than appending.
Reports
White-label report pages and their custom domains.
/reportsnot implementedfreeCreate a branded report
Requires an active reporting subscription and a valid project_id. Supplying `custom_domain` registers the domain and provisions SSL automatically.
/reportsnot implementedfreeList reports
/reports/{id}not implementedfreeRetrieve a report
/reports/{id}not implementedfreeUpdate branding, domain or active state
Webhooks
Get pushed updates instead of polling.
/webhooksnot implementedfreeRegister an endpoint
Returns a signing secret, shown once.
Webhooks
Get told instead of asking
The plan: register an endpoint and we push order events to it, retrying a failed delivery up to five times with exponential backoff, starting a minute out, before dropping it.
Outbound webhooks are not sending
Nothing is delivered to registered endpoints today. This is separate from the endpoints above: even once the REST routes ship, the delivery worker has to be built before any of the events below arrive.
Events
- order.created
- An order was accepted and queued.
- order.status_changed
- Any order-level status transition.
- order.item_delivered
- A single placement went live and verified.
- order.completed
- Every item on the order is delivered.
- order.fully_delivered
- Managed budget: every child order finished.
- order.failed
- An item could not be delivered and was refunded.
- order.message_received
- Staff replied on an order thread.
Verifying a delivery
Every request carries a X-LY-Signature header. Recompute the HMAC over {timestamp}.{rawBody} using your signing secret and compare in constant time. Reject anything older than five minutes.
X-LY-Signature: t=1786377600,v1=8f3c...
// Node
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(header, rawBody, secret) {
const parts = Object.fromEntries(
header.split(",").map((kv) => kv.split("="))
);
const expected = createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
const age = Math.abs(Date.now() / 1000 - Number(parts.t));
if (age > 300) return false;
return timingSafeEqual(
Buffer.from(parts.v1),
Buffer.from(expected)
);
}Use the raw request body, not a re-serialized JSON object — any whitespace difference changes the signature.
Errors
The envelope every error will use. None of these codes is reachable yet — an unbuilt route answers with a plain 404.
{
"error": {
"code": "validation_failed",
"message": "intake.target_url must be a valid URL",
"fields": ["items.0.intake.target_url"]
}
}- 400 invalid_request
- Malformed body, unsupported file type, or an unavailable product.
- 401 unauthorized
- Missing, malformed or revoked API key.
- 402 insufficient_balance
- Top up, or lower the quantity.
- 402 subscription_required
- Reports need an active reporting plan.
- 404 not_found
- No such resource, or it belongs to another account.
- 409 conflict
- Slug or custom domain already in use.
- 422 validation_failed
- Intake did not match the product's schema. See `error.fields`.
- 429 rate_limited
- Back off; check the Retry-After header.
- 500 server_error
- Our fault. Safe to retry with the same idempotency key.
Limits
What the API will enforce once it ships. Nothing enforces them today.
Rate limits
- All endpoints
- 60 requests / minute per key
- GET /marketplace/listings
- 20 requests / 24 hours per account
- POST /intake-attachments
- 100 uploads / hour per account
Account caps
- Projects per account
- 150
- Reports per account
- 150
- Tracked keywords
- By plan
- Attachment size
- 10 MB
Idempotency
Pass a unique idempotency_key on every order. A retry with the same key returns the original order instead of creating a second one — which matters, because order creation moves money.
Keys are not being issued yet
Key creation is paused until the endpoints exist. A key issued today would authenticate nothing, and because a key is shown only once it would have to be reissued anyway. When the routes ship, the Developers tab in your dashboard is where keys will live. Until then, the fastest way to influence what gets built first is to tell us what you need from it.