{"openapi":"3.2.0","info":{"title":"LinksWatcher API","description":"Automate URL scans across websites and pages to monitor link health, detect broken links, and simplify recurring affiliate audits.","version":"1.0.0"},"servers":[{"url":"https://fljdt.jojapi.net"}],"security":[{"ApiKeyAuth":[]}],"tags":[{"name":"Scanning"}],"x-agent-commerce":{"catalog_url":"https://fljdt.jojapi.net/_jojapi/agent/plans","discovery_url":"https://fljdt.jojapi.net/.well-known/x402","directory_url":"https://agents.jojapi.net/apis/linkswatcher-api","docs":"https://docs.jojapi.com/consumers/agents"},"paths":{"/api/scanurl":{"post":{"operationId":"post_api_scanurl","summary":"Submit URLs for scanning","description":"Submit a list of URLs to be scanned. Returns a `batchId` immediately — scanning happens asynchronously.\n\n**Workflow:**\n1. POST this endpoint with your URLs → receive `batchId`\n2. Poll `GET /api/batch/{id}` every few seconds until `status === \"COMPLETED\"`\n3. Or configure a webhook to receive push notifications\n\n**Batch size** is capped by your plan (5–100 URLs per request). Split large lists into multiple requests.\n\nIf you want batch results to include page screenshots, poll `GET /api/batch/{id}?includeScreenshot=true`.\n\n**Domains** must be in your plan's allowed domain list. Requests containing disallowed domains will be rejected with `403`.","tags":["Scanning"],"responses":{"202":{"description":"Batch accepted. Scanning is in progress asynchronously. Use the returned `batchId` to poll for results.","content":{"application/json":{"schema":{"type":"object","properties":{"batchId":{"type":"string","format":"uuid","description":"Unique identifier for this batch. Use it with `GET /api/batch/{id}` to retrieve results.","example":"550e8400-e29b-41d4-a716-446655440000"}}},"example":{"batchId":"550e8400-e29b-41d4-a716-446655440000"}}}},"400":{"description":"Invalid request body — missing or malformed `urls`, invalid `market`, or invalid `webhookIds`.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message","example":"Batch not found"}},"required":["error"]},"example":{"error":"urls must be a non-empty array"}}}},"401":{"description":"Missing or invalid Bearer token.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message","example":"Batch not found"}},"required":["error"]},"example":{"error":"Unauthorized"}}}},"402":{"description":"Insufficient credit balance to cover overage costs.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message","example":"Batch not found"}},"required":["error"]},"example":{"error":"Insufficient credit balance. Top up at https://console.linkswatcher.com/subscription"}}}},"403":{"description":"One or more URLs belong to a domain that is not in your allowed domains list.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message","example":"Batch not found"}},"required":["error"]},"example":{"error":"Domain not allowed: example.com"}}}},"429":{"description":"Rate limit exceeded. Slow down your request rate or upgrade your plan.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message","example":"Batch not found"}},"required":["error"]},"example":{"error":"Rate limit exceeded"}}}}},"requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["urls"],"properties":{"urls":{"type":"array","items":{"type":"string","format":"uri"},"minItems":1,"maxItems":100,"description":"List of URLs to scan. Maximum count is determined by your plan's batch size.","example":["https://amzn.to/3abc123","https://www.amazon.fr/dp/B08N5WRWNW"]},"market":{"type":"string","default":"FR","description":"Locale/market code used to interpret prices and availability. Affects the geolocation of the scan request.\n\nSupported values: `FR`, `US`, `GB`, `DE`, `ES`, `IT`, `CA`, `JP`, and others.","example":"FR"},"webhookIds":{"type":"array","items":{"type":"string","format":"uuid"},"description":"IDs of webhooks to notify when all jobs in this batch are finished. Webhook IDs are available in your console under **Webhooks**. If omitted, no webhook is triggered.","example":["a1b2c3d4-e5f6-7890-abcd-ef1234567890"]}}},"example":{"urls":["https://amzn.to/3abc123"]}}}}}},"/api/batch/{id}":{"get":{"operationId":"get_api_batch_id","summary":"Get batch status and results","description":"Poll this endpoint to check the progress of a batch and retrieve completed results.\n\n**Polling strategy:** We recommend polling every **2–5 seconds**. The batch reaches a terminal state (`COMPLETED` or `FAILED`) once `finishedJobs === expectedJobs`.\n\nSet `includeScreenshot=true` if you also want each completed job to include `result.screenshotBase64`.\n\n**Tip:** Instead of polling, configure a **webhook** on the batch to receive a push notification when it finishes.","tags":["Scanning"],"responses":{"200":{"description":"Batch details including all jobs and their scan results.","content":{"application/json":{"schema":{"type":"object","properties":{"batchId":{"type":"string","format":"uuid","description":"Unique identifier of this batch."},"status":{"type":"string","enum":["PENDING","RUNNING","COMPLETED","FAILED"],"description":"Overall batch status. Transitions: `PENDING → RUNNING → COMPLETED` (or `FAILED` if all jobs fail)."},"expectedJobs":{"type":"integer","description":"Total number of jobs in this batch.","example":10},"finishedJobs":{"type":"integer","description":"Number of jobs that have reached a terminal state (COMPLETED or FAILED).","example":7},"createdAt":{"type":"string","format":"date-time"},"jobs":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Unique job identifier."},"url":{"type":"string","format":"uri","description":"The URL that was submitted for scanning.","example":"https://amzn.to/3abc123"},"market":{"type":"string","description":"Market/locale used for this job.","example":"FR"},"status":{"type":"string","enum":["PENDING","RUNNING","COMPLETED","FAILED"],"description":"Current processing state of the job."},"attemptCount":{"type":"integer","description":"Number of scan attempts made so far (max 3 before marking as FAILED).","example":1},"result":{"type":"object","nullable":true,"description":"Populated once the job has finished scanning. `null` while the job is still `PENDING` or `RUNNING`.","properties":{"state":{"type":"string","enum":["OK","REDIRECT","BROKEN","OUT_OF_STOCK","UNAVAILABLE","ERROR"],"description":"High-level outcome of the scan.\n\n| Value | Meaning |\n|---|---|\n| `OK` | Link is live and the product is in stock |\n| `REDIRECT` | Link redirects to a different final URL |\n| `BROKEN` | HTTP 4xx / 5xx or connection error |\n| `OUT_OF_STOCK` | Product found but not purchasable |\n| `UNAVAILABLE` | Page exists but product is gone |\n| `ERROR` | Internal scan error — retry recommended |"},"finalUrl":{"type":"string","format":"uri","description":"Final URL after all redirects have been followed.","example":"https://www.amazon.fr/dp/B08N5WRWNW"},"httpStatus":{"type":"integer","description":"HTTP status code of the final response.","example":200},"responseMs":{"type":"integer","description":"Total response time in milliseconds.","example":843},"productName":{"type":"string","nullable":true,"description":"Product title extracted from the page, if detected.","example":"Echo Dot (4th Gen) | Smart speaker with Alexa"},"productImageUrl":{"type":"string","format":"uri","nullable":true,"description":"Main product image URL, if detected."},"price":{"type":"object","nullable":true,"description":"Current price of the product. `null` if no price was detected.","properties":{"amount":{"type":"number","description":"Price value.","example":29.99},"currency":{"type":"string","description":"ISO 4217 currency code.","example":"EUR"}}},"inStock":{"type":"boolean","nullable":true,"description":"`true` if the product is available to purchase, `false` if out of stock, `null` if not determinable.","example":true},"stockLevel":{"type":"string","nullable":true,"description":"Stock availability description returned by the retailer, if available.","example":"In Stock"},"affiliateDetected":{"type":"boolean","description":"Whether the URL contains a recognised affiliate tracking parameter.","example":true},"replacementProduct":{"type":"object","nullable":true,"description":"When a product is discontinued or out of stock the retailer sometimes redirects to a replacement. This field captures those details.","properties":{"url":{"type":"string","format":"uri"},"name":{"type":"string","nullable":true}}},"aiConfidence":{"type":"number","minimum":0,"maximum":1,"nullable":true,"description":"Confidence score (0–1) of the AI extraction model.","example":0.97},"diagnostic":{"type":"string","nullable":true,"description":"Short diagnostic message from the scanner, useful for debugging unexpected states.","example":"Detected 301 redirect chain (3 hops)"},"screenshotBase64":{"type":"string","nullable":true,"description":"Base64-encoded page screenshot captured during the scan. This field is omitted unless you call `GET /api/batch/{id}?includeScreenshot=true`.","example":"/9j/4AAQSkZJRgABAQAAAQABAAD..."},"domain":{"type":"string","description":"Effective domain of the final URL.","example":"amazon.fr"},"market":{"type":"string","description":"Market code used for this scan.","example":"FR"},"checkedAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp of when the scan completed."},"duration":{"type":"integer","description":"Total scan duration in milliseconds (including retries).","example":1250}}}}}}}},"example":{"batchId":"550e8400-e29b-41d4-a716-446655440000","status":"COMPLETED","expectedJobs":2,"finishedJobs":2,"createdAt":"2026-04-06T10:00:00.000Z","jobs":[{"id":"661f9511-f30c-42e5-b827-557766551111","url":"https://amzn.to/3abc123","market":"FR","status":"COMPLETED","attemptCount":1,"result":{"state":"OK","finalUrl":"https://www.amazon.fr/dp/B08N5WRWNW","httpStatus":200,"responseMs":843,"productName":"Echo Dot (4th Gen)","productImageUrl":"https://m.media-amazon.com/images/I/example.jpg","price":{"amount":29.99,"currency":"EUR"},"inStock":true,"stockLevel":"In Stock","affiliateDetected":true,"replacementProduct":null,"aiConfidence":0.97,"diagnostic":null,"screenshotBase64":"/9j/4AAQSkZJRgABAQAAAQABAAD...","domain":"amazon.fr","market":"FR","checkedAt":"2026-04-06T10:00:03.000Z","duration":1250}}]}}}},"400":{"description":"The provided `id` is not a valid UUID.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message","example":"Batch not found"}},"required":["error"]},"example":{"error":"Invalid batch ID format"}}}},"401":{"description":"Missing or invalid Bearer token.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message","example":"Batch not found"}},"required":["error"]},"example":{"error":"Unauthorized"}}}},"404":{"description":"No batch found with this ID for your account.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message","example":"Batch not found"}},"required":["error"]},"example":{"error":"Batch not found"}}}}},"parameters":[{"name":"id","in":"path","description":"The `batchId` returned by `POST /api/scanurl` or `POST /api/discover`.","required":true,"schema":{"type":"string","example":"550e8400-e29b-41d4-a716-446655440000"}},{"name":"includeScreenshot","in":"query","description":"When `true`, each completed job includes `result.screenshotBase64` with the captured page screenshot.","required":false,"schema":{"type":"boolean","example":true,"default":false}}]}}},"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-JoJAPI-Key","description":"Your JoJ API key."}}}}