{"openapi":"3.1.0","info":{"title":"Hack Club NDA Status API","version":"v1","description":"Public API for checking whether a Hack Club member has signed the current NDA."},"servers":[{"url":"https://nda.hackclub.com"}],"paths":{"/api/v1/nda_status/{info}":{"get":{"operationId":"getNdaStatus","summary":"Check a member's NDA status","description":"Accepts a Slack ID or URL-encoded email (for example orpheus%40example.com). Unknown identifiers return not_signed. Email lookup checks signing and verified account emails, ignoring case and surrounding whitespace, using recorded signatures only. Email responses omit Slack ID. Each client IP shares 10 requests per one-second window across the site, including failed requests. All status responses use private, no-store to keep quota headers current. Wait for RateLimit-Reset when RateLimit-Remaining is zero; on HTTP 429 wait at least Retry-After seconds before retrying.","security":[],"parameters":[{"name":"info","in":"path","required":true,"description":"A Slack member ID beginning with U or W, or a URL-encoded email address.","schema":{"oneOf":[{"type":"string","pattern":"^[UW][A-Z0-9]{8,20}$"},{"type":"string","format":"email","maxLength":254}]},"example":"U0123ABCDEF"}],"responses":{"200":{"description":"The member's status for the current NDA version.","headers":{"RateLimit-Limit":{"description":"Request quota per client IP per window.","schema":{"type":"integer","example":10}},"RateLimit-Remaining":{"description":"Requests remaining after this request, clamped to zero.","schema":{"type":"integer","minimum":0,"example":9}},"RateLimit-Reset":{"description":"Seconds until the window resets (not a Unix timestamp).","schema":{"type":"integer","minimum":1,"example":1}},"RateLimit-Policy":{"description":"Quota policy in the IETF draft format.","schema":{"type":"string","example":"\"requests-per-ip\";q=10;w=1"}},"RateLimit":{"description":"Remaining quota and seconds to reset in the IETF draft format.","schema":{"type":"string","example":"\"requests-per-ip\";r=9;t=1"}},"Cache-Control":{"description":"private, no-store: responses and per-IP quota headers must not be cached.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/NdaStatus"},{"$ref":"#/components/schemas/NdaStatusByEmail"}]},"examples":{"signed":{"summary":"Signed","value":{"slack_id":"U0123ABCDEF","status":"signed","nda_version":"2026-09-17","signed_at":"2025-04-02T15:04:05Z","signature_type":"native"}},"imported":{"summary":"Signed, imported from the previous signing system","value":{"slack_id":"U0123ABCDEF","status":"signed","nda_version":"2025-04-01","signed_at":"2025-12-04T23:22:32Z","signature_type":"legacy"}},"not_signed":{"summary":"Not signed or unknown","value":{"slack_id":"U0123ABCDEF","status":"not_signed","nda_version":"2026-09-17"}}}}}},"400":{"description":"The Slack ID or email is malformed (invalid_slack_id or invalid_email).","headers":{"RateLimit-Limit":{"description":"Request quota per client IP per window.","schema":{"type":"integer","example":10}},"RateLimit-Remaining":{"description":"Requests remaining after this request, clamped to zero.","schema":{"type":"integer","minimum":0,"example":9}},"RateLimit-Reset":{"description":"Seconds until the window resets (not a Unix timestamp).","schema":{"type":"integer","minimum":1,"example":1}},"RateLimit-Policy":{"description":"Quota policy in the IETF draft format.","schema":{"type":"string","example":"\"requests-per-ip\";q=10;w=1"}},"RateLimit":{"description":"Remaining quota and seconds to reset in the IETF draft format.","schema":{"type":"string","example":"\"requests-per-ip\";r=9;t=1"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"invalid_slack_id"}}}},"429":{"description":"The client IP exceeded 10 requests in one second.","headers":{"RateLimit-Limit":{"description":"Request quota per client IP per window.","schema":{"type":"integer","example":10}},"RateLimit-Remaining":{"description":"Requests remaining after this request, clamped to zero.","schema":{"type":"integer","minimum":0,"example":9}},"RateLimit-Reset":{"description":"Seconds until the window resets (not a Unix timestamp).","schema":{"type":"integer","minimum":1,"example":1}},"RateLimit-Policy":{"description":"Quota policy in the IETF draft format.","schema":{"type":"string","example":"\"requests-per-ip\";q=10;w=1"}},"RateLimit":{"description":"Remaining quota and seconds to reset in the IETF draft format.","schema":{"type":"string","example":"\"requests-per-ip\";r=9;t=1"}},"Retry-After":{"description":"Seconds until the client can retry.","schema":{"type":"integer","minimum":1}}},"content":{"text/plain":{"schema":{"type":"string"},"example":"Retry later\n"}}}}}}},"components":{"schemas":{"NdaStatus":{"type":"object","additionalProperties":false,"required":["slack_id","status","nda_version"],"properties":{"status":{"type":"string","enum":["signed","not_signed"]},"nda_version":{"type":"string","description":"The NDA version checked."},"signed_at":{"type":"string","format":"date-time","description":"Present only when status is signed."},"signature_type":{"type":"string","enum":["native","legacy"],"description":"How the agreement was signed. Present only when status is signed. native: completed via the new signing flow. legacy: a completed & cryptographically valid PDF imported from the previous system. Both are legally binding agreements, but it is recommended that users complete the new signing flow. If you check NDAs for a user that has signed via the legacy system, you may want to prompt them to sign again via the new flow, but do not block them from using your service if they have a valid legacy signature."},"slack_id":{"type":"string","description":"The normalized uppercase Slack member ID."}}},"NdaStatusByEmail":{"type":"object","additionalProperties":false,"required":["status","nda_version"],"properties":{"status":{"type":"string","enum":["signed","not_signed"]},"nda_version":{"type":"string","description":"The NDA version checked."},"signed_at":{"type":"string","format":"date-time","description":"Present only when status is signed."},"signature_type":{"type":"string","enum":["native","legacy"],"description":"How the agreement was signed. Present only when status is signed. native: completed via the new signing flow. legacy: a completed & cryptographically valid PDF imported from the previous system. Both are legally binding agreements, but it is recommended that users complete the new signing flow. If you check NDAs for a user that has signed via the legacy system, you may want to prompt them to sign again via the new flow, but do not block them from using your service if they have a valid legacy signature."}}},"Error":{"type":"object","additionalProperties":false,"required":["error"],"properties":{"error":{"type":"string","enum":["invalid_slack_id","invalid_email"]}}}}}}