{"openapi":"3.1.0","info":{"title":"NADS API Console Vendor API","version":"1.0.0","description":"Browse published projects, register your end links, and track your own delivery."},"servers":[{"url":"https://api.responads.com"}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"X-API-Key"},"bearer":{"type":"http","scheme":"bearer"}}},"paths":{"/api/v1/redirects":{"get":{"operationId":"redirects","summary":"Read your redirects and our outcome URLs","description":"Your return URLs receive our signed outcomes. Our outcome URLs belong to the upstream return leg; they are not respondent entry links. Each imported project supplies its own entryLink.","security":[{"apiKey":[]},{"bearer":[]}],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"success":true,"returnUrls":{"completeUrl":"https://panel.example/end?pid={{PID}}&s=1"},"outcomeUrls":{"completeUrl":"https://console.example/surveyStatus?status=1&pid={{PID}}"}}}}}}},"post":{"operationId":"save-redirects","summary":"Save your four default return URLs","description":"Available to API keys and portal owners. All four URLs must use approved domains and include {{PID}}. New imports use these defaults when fields are omitted. Re-import existing projects to update future entries; respondents already in flight keep their original return route.","security":[{"apiKey":[]},{"bearer":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["completeUrl","terminateUrl","quotaFullUrl","securityTermUrl"],"properties":{"completeUrl":{"type":"string","description":"Approved return URL containing {{PID}}."},"terminateUrl":{"type":"string","description":"Approved return URL containing {{PID}}."},"quotaFullUrl":{"type":"string","description":"Approved return URL containing {{PID}}."},"securityTermUrl":{"type":"string","description":"Approved return URL containing {{PID}}."}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"success":true,"returnUrls":{"completeUrl":"https://panel.example/end?pid={{PID}}&s=1"}}}}}}}},"/api/v1/respondents":{"get":{"operationId":"respondents","summary":"Your respondent journeys","description":"Only your traffic. Includes your original respondent ID, our session ID, start/end IP, timestamps and outcome. handedOffAt records a redirect issued by us, not acknowledgment from your server. Remote vendor/client IP logs remain in those systems.","security":[{"apiKey":[]},{"bearer":[]}],"parameters":[{"name":"importId","in":"query","required":false,"description":"Optional owned import ID.","schema":{"type":"string"}},{"name":"respondentId","in":"query","required":false,"description":"Exact original respondent ID.","schema":{"type":"string"}},{"name":"page","in":"query","required":false,"description":"1-based page.","schema":{"type":"integer"}},{"name":"limit","in":"query","required":false,"description":"At most 100, defaults to 25.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"success":true,"respondents":[{"sessionId":"R...","respondentId":"your-person-123","importId":"...","status":"Completed","startIp":"192.0.2.1","endIp":"192.0.2.2","handoffStatus":"returned"}],"pagination":{"page":1,"limit":25,"total":1,"totalPages":1}}}}}}}},"/api/v1/projects/{publicId}/qualifications":{"get":{"operationId":"qualifications","summary":"Survey screening qualifications","description":"Sanitized targeting data for a listing visible to your account. available:false means this project's source does not expose this data; do not interpret an empty array as unrestricted eligibility. Responses may be cached for five minutes.","security":[{"apiKey":[]},{"bearer":[]}],"parameters":[{"name":"publicId","in":"path","required":true,"description":"Public listing ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"success":true,"available":true,"data":[{"name":"AGE","type":"Range","questionText":"Your age","required":true,"range":"18-65","options":[]}]}}}}}}},"/api/v1/projects/{publicId}/quotas":{"get":{"operationId":"quotas","summary":"Survey quotas","description":"Sanitized targeting data for a listing visible to your account. available:false means this project's source does not expose this data; do not interpret an empty array as unrestricted eligibility. Responses may be cached for five minutes.","security":[{"apiKey":[]},{"bearer":[]}],"parameters":[{"name":"publicId","in":"path","required":true,"description":"Public listing ID.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"success":true,"available":true,"data":[{"name":"All respondents","limit":100,"remaining":40,"status":"Open"}]}}}}}}},"/api/vendor/auth/register":{"post":{"operationId":"register","summary":"Apply for a partner account","description":"Creates a pending partner account. An operator reviews the company and return domains before enabling login and API access.","security":[],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["company","fullName","email","password","website","returnDomains"],"properties":{"company":{"type":"string","description":"Company name."},"fullName":{"type":"string","description":"Account owner's full name."},"email":{"type":"string","description":"Owner email."},"password":{"type":"string","description":"At least 10 characters, at most 72 UTF-8 bytes."},"website":{"type":"string","description":"Company website URL."},"returnDomains":{"type":"string","description":"Comma-separated return domains for operator approval."}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"success":true,"message":"Your application is submitted."}}}}}}},"/api/vendor/auth/login":{"post":{"operationId":"login","summary":"Portal sign-in","description":"Exchanges portal credentials for a session token. Only needed if you drive the API as a person rather than with a key - an API key does everything this token does.","security":[],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","description":"Portal user email."},"password":{"type":"string","description":"Portal user password."}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"success":true,"token":"eyJhbGciOiJIUzI1NiIs...","expiresInSeconds":604800,"vendor":{"name":"Acme Supply","status":"Active"},"user":{"fullName":"Dana Reyes","email":"dana@acme.com","role":"owner"}}}}}}}},"/api/v1/me":{"get":{"operationId":"me","summary":"Account and delivery summary","description":"Confirms the key works and echoes back your approved return domains. Run this first: registering an end link on a host nobody approved is the commonest failure in a new integration, and this shows the allowlist before you hit it.","security":[{"apiKey":[]},{"bearer":[]}],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"success":true,"vendor":{"name":"Acme Supply","status":"Active","allowedReturnDomains":["panel.acme.com"],"contactEmail":"ops@acme.com"},"authenticatedVia":"apiKey","activeImports":3,"delivery":{"inProgress":12,"completes":480,"terminates":910,"quotaFulls":60,"securityTerminates":4,"hits":1454,"incidenceRate":34.5}}}}}}}},"/api/v1/reference":{"get":{"operationId":"reference","summary":"Status codes, placeholders and signing rules","description":"Everything an integration hard-codes, in one call: which outcome code calls which of your URLs, the placeholder tokens we substitute, and the canonicalization used for exit_hmac.","security":[{"apiKey":[]},{"bearer":[]}],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"success":true,"statusCodes":[{"code":1,"name":"Completed","returnUrl":"completeUrl"},{"code":2,"name":"DisQualified","returnUrl":"terminateUrl"},{"code":3,"name":"QuotaFull","returnUrl":"quotaFullUrl"},{"code":4,"name":"SecurityTerm","returnUrl":"securityTermUrl"}]}}}}}}},"/api/v1/projects":{"get":{"operationId":"projects","summary":"Browse the catalog","description":"Live listings you are allowed to see. Invite-only work you were not offered does not appear, and is not counted in the total.","security":[{"apiKey":[]},{"bearer":[]}],"parameters":[{"name":"search","in":"query","required":false,"description":"Case-insensitive match on the title.","schema":{"type":"string"}},{"name":"country","in":"query","required":false,"description":"Matches the primary country or any target country.","schema":{"type":"string"}},{"name":"page","in":"query","required":false,"description":"1-based. Defaults to 1.","schema":{"type":"integer"}},{"name":"limit","in":"query","required":false,"description":"Defaults to 25, capped at 100.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"success":true,"projects":[{"id":"SE-1A2B3C4D","title":"Automotive owners study","country":"US","loi":12,"incidenceRate":35,"completesNeeded":500,"cpi":3.2,"currency":"USD","status":"Listed","version":2}],"pagination":{"page":1,"limit":25,"total":1,"totalPages":1}}}}}}}},"/api/v1/projects/{id}":{"get":{"operationId":"project","summary":"One listing","description":"Full detail, plus whether you already hold it. A listing you were not offered answers 404, the same as one that does not exist - we do not confirm the id is real.","security":[{"apiKey":[]},{"bearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The public listing id, e.g. SE-1A2B3C4D.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"success":true,"project":{"id":"SE-1A2B3C4D","title":"Automotive owners study","cpi":3.2},"imported":{"importId":"665f...","entryLink":"https://.../s/start?gid=...&sid=...&pid=","status":"Active","stale":false}}}}}}}},"/api/v1/projects/{id}/import-preview":{"post":{"operationId":"import-preview","summary":"Dry-run an import","description":"Same validation as the import, no writes. Use it to prove your end links pass the domain allowlist and to see which outcome calls which URL before any respondent is involved.","security":[{"apiKey":[]},{"bearer":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":[],"properties":{"completeUrl":{"type":"string","description":"Overrides the account default. Where a completed respondent is returned."},"terminateUrl":{"type":"string","description":"Overrides the account default. Where a screened-out respondent is returned."},"quotaFullUrl":{"type":"string","description":"Overrides the account default. Where a respondent is returned when the cell is full."},"securityTermUrl":{"type":"string","description":"Overrides the account default. Where a respondent is returned after a failed fraud check."}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"success":true,"listingId":"SE-1A2B3C4D","version":2,"outcomeMapping":{"1":{"outcome":"Complete","url":"https://panel.acme.com/end?pid={{PID}}&s=1"}},"alreadyImported":false,"entryLink":null}}}}}}},"/api/v1/projects/{id}/import":{"post":{"operationId":"import","summary":"Register end links, receive an entry link","description":"Import into your console and receive its tracked entry link. All four URLs must be available, either from account defaults or this request. Send {} to use defaults. Re-importing updates future entries and keeps the same entry link; journeys already in flight keep their original return URLs.","security":[{"apiKey":[]},{"bearer":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":[],"properties":{"completeUrl":{"type":"string","description":"Overrides the account default. Where a completed respondent is returned."},"terminateUrl":{"type":"string","description":"Overrides the account default. Where a screened-out respondent is returned."},"quotaFullUrl":{"type":"string","description":"Overrides the account default. Where a respondent is returned when the cell is full."},"securityTermUrl":{"type":"string","description":"Overrides the account default. Where a respondent is returned after a failed fraud check."}}}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"success":true,"project":{"id":"SE-1A2B3C4D","title":"Automotive owners study","cpi":3.2},"importId":"665f1c0e9a2b4c00123abc45","entryLink":"https://console.nads.com/s/start?gid=UDAwMQ&sid=9F3A21C0B4E7&pid=","entryCode":"9F3A21C0B4E7","listingVersion":2,"cpi":3.2,"currency":"USD"}}}}}}},"/api/v1/imports":{"get":{"operationId":"imports","summary":"Your imports","description":"Every project you hold, with the entry link and the terms it was taken under. `stale: true` means the listing has been re-published since - your copy of the terms is out of date.","security":[{"apiKey":[]},{"bearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"description":"Active, Paused or Revoked.","schema":{"type":"string"}},{"name":"page","in":"query","required":false,"description":"1-based. Defaults to 1.","schema":{"type":"integer"}},{"name":"limit","in":"query","required":false,"description":"Defaults to 25, capped at 100.","schema":{"type":"integer"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"success":true,"imports":[{"importId":"665f1c0e9a2b4c00123abc45","listingId":"SE-1A2B3C4D","title":"Automotive owners study","entryLink":"https://console.nads.com/s/start?gid=UDAwMQ&sid=9F3A21C0B4E7&pid=","status":"Active","cpi":3.2,"currency":"USD","stale":false}],"pagination":{"page":1,"limit":25,"total":1,"totalPages":1}}}}}}}},"/api/v1/imports/{importId}/stats":{"get":{"operationId":"import-stats","summary":"Your delivery on one import","description":"Aggregated from the same respondent rows our own operations pages read, with the same metric definitions - our number and yours can only differ because of traffic still in flight. `unreturnedCompletes` is the reconciliation figure: completes we recorded for which we have not issued a return redirect. Issuing a redirect does not confirm your server received it.","security":[{"apiKey":[]},{"bearer":[]}],"parameters":[{"name":"importId","in":"path","required":true,"description":"From the import response or /v1/imports.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"example":{"success":true,"importId":"665f1c0e9a2b4c00123abc45","listingId":"SE-1A2B3C4D","stats":{"inProgress":4,"completes":160,"terminates":300,"quotaFulls":21,"securityTerminates":1,"hits":482,"incidenceRate":34.8,"conversionRate":33.2},"reconciliation":{"completesHandedBack":160,"unreturnedCompletes":0},"earnings":{"cpi":3.2,"currency":"USD","gross":512}}}}}}}}}}