{"openapi":"3.1.0","info":{"title":"WiraPass Integration API","version":"0.10.4","summary":"Push approvals and backup codes for your sign-in, through a Duo-compatible Auth API or a simple JSON API.","description":"Two APIs over the same WiraPass approvals:\n\n- **Duo-compatible Auth API** under `/auth/v2/`: requests are signed like Duo's Auth API v2 (HTTP Basic with the integration key and an HMAC of the canonical request made with the secret key) and answers use Duo's envelope. Systems built on Duo's official client libraries switch by changing the API hostname, the integration key and the secret key.\n- **Simple JSON API** under `/v1/integrations/`: a Bearer API key and plain JSON.\n\nSigned requests carry a `Date` header no more than 5 minutes away from the server clock. Request bodies are limited to 128 KiB. Full documentation: https://auth.wiracode.com/developers\n\nDuo is a trademark of Cisco. WiraPass is not affiliated with Duo or Cisco and only offers API compatibility.","contact":{"name":"WiraPass","email":"support@wiracode.com","url":"https://auth.wiracode.com/developers"}},"servers":[{"url":"https://auth.wiracode.com"}],"externalDocs":{"description":"WiraPass developer documentation","url":"https://auth.wiracode.com/developers"},"tags":[{"name":"Duo-compatible Auth API","description":"Duo Auth API v2 paths, parameters, signing and envelope.","externalDocs":{"url":"https://auth.wiracode.com/developers/auth-api"}},{"name":"Simple JSON API","description":"Bearer API key, plain JSON.","externalDocs":{"url":"https://auth.wiracode.com/developers/simple-api"}}],"paths":{"/auth/v2/ping":{"get":{"operationId":"duoPing","tags":["Duo-compatible Auth API"],"summary":"Liveness Check","description":"No signature needed. Use it to see that the service answers and to compare clocks.","security":[],"responses":{"200":{"description":"Server time (Unix seconds).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DuoTimeOk"},"example":{"stat":"OK","response":{"time":1790497020}}}}}}}},"/auth/v2/check":{"get":{"operationId":"duoCheck","tags":["Duo-compatible Auth API"],"summary":"Check Keys and Signature","description":"Answers OK when the integration key, the secret key and your signing are right.","security":[{"duoSignature":[]}],"parameters":[{"$ref":"#/components/parameters/DuoDate"}],"responses":{"200":{"description":"Server time (Unix seconds).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DuoTimeOk"},"example":{"stat":"OK","response":{"time":1790497020}}}}},"401":{"$ref":"#/components/responses/DuoUnauthorized"},"429":{"$ref":"#/components/responses/DuoTooManyRequests"},"503":{"$ref":"#/components/responses/DuoUnavailable"}}}},"/auth/v2/logo":{"get":{"operationId":"duoLogo","tags":["Duo-compatible Auth API"],"summary":"Logo","description":"The WiraPass icon as a 128 x 128 PNG, to show on your sign-in page.","security":[{"duoSignature":[]}],"parameters":[{"$ref":"#/components/parameters/DuoDate"}],"responses":{"200":{"description":"PNG image.","content":{"image/png":{"schema":{"type":"string","contentMediaType":"image/png"}}}},"401":{"$ref":"#/components/responses/DuoUnauthorized"},"429":{"$ref":"#/components/responses/DuoTooManyRequests"},"503":{"$ref":"#/components/responses/DuoUnavailable"}}}},"/auth/v2/enroll":{"post":{"operationId":"duoEnroll","tags":["Duo-compatible Auth API"],"summary":"Start Enrolling a User","description":"WiraPass users enroll themselves: they install the app, sign in with this email and turn on approvals. This endpoint returns the download page, a QR code of it and an activation code tied to the email, to be checked with enroll_status.\n\nWhen no WiraPass account exists for the email yet, user_id is a temporary id that only works with enroll_status. Use username with preauth and auth.","security":[{"duoSignature":[]}],"parameters":[{"$ref":"#/components/parameters/DuoDate"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"username":{"type":"string","format":"email","examples":["rina@example.com"],"description":"The email the user will sign in to WiraPass with. Required in WiraPass."},"valid_secs":{"type":["string","integer"],"pattern":"^[0-9]+$","examples":[86400],"description":"How long the activation code stays valid, in seconds. Default 86400, clamped to 60 to 604800."}},"required":["username"]},"example":{"username":"rina@example.com","valid_secs":86400}},"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"username":{"type":"string","format":"email","examples":["rina@example.com"],"description":"The email the user will sign in to WiraPass with. Required in WiraPass."},"valid_secs":{"type":["string","integer"],"pattern":"^[0-9]+$","examples":[86400],"description":"How long the activation code stays valid, in seconds. Default 86400, clamped to 60 to 604800."}},"required":["username"]}}}},"responses":{"200":{"description":"Activation data.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DuoEnrollOk"},"example":{"stat":"OK","response":{"activation_barcode":"https://auth.wiracode.com/auth/v2/activation_qr","activation_code":"q3Zb8Xk1LwT0vY7uR5nM2pQ9sD4fG6hJ","activation_url":"https://auth.wiracode.com/#download","expiration":1790583420,"user_id":"0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d","username":"rina@example.com"}}}}},"400":{"$ref":"#/components/responses/DuoBadRequest"},"401":{"$ref":"#/components/responses/DuoUnauthorized"},"429":{"$ref":"#/components/responses/DuoTooManyRequests"},"503":{"$ref":"#/components/responses/DuoUnavailable"}}}},"/auth/v2/enroll_status":{"post":{"operationId":"duoEnrollStatus","tags":["Duo-compatible Auth API"],"summary":"Enrollment Status","description":"Answers success once that email has an active approval phone, waiting until then, and invalid when the code is unknown, expired, belongs to another integration or the user_id does not match.","security":[{"duoSignature":[]}],"parameters":[{"$ref":"#/components/parameters/DuoDate"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"user_id":{"type":"string","examples":["0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d"],"description":"The user_id from enroll."},"activation_code":{"type":"string","examples":["q3Zb8Xk1LwT0vY7uR5nM2pQ9sD4fG6hJ"],"description":"The activation_code from enroll."}},"required":["user_id","activation_code"]},"example":{"activation_code":"q3Zb8Xk1LwT0vY7uR5nM2pQ9sD4fG6hJ"}},"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"user_id":{"type":"string","examples":["0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d"],"description":"The user_id from enroll."},"activation_code":{"type":"string","examples":["q3Zb8Xk1LwT0vY7uR5nM2pQ9sD4fG6hJ"],"description":"The activation_code from enroll."}},"required":["user_id","activation_code"]}}}},"responses":{"200":{"description":"One of success, waiting or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DuoEnrollStatusOk"},"example":{"stat":"OK","response":"waiting"}}}},"400":{"$ref":"#/components/responses/DuoBadRequest"},"401":{"$ref":"#/components/responses/DuoUnauthorized"},"429":{"$ref":"#/components/responses/DuoTooManyRequests"},"503":{"$ref":"#/components/responses/DuoUnavailable"}}}},"/auth/v2/preauth":{"post":{"operationId":"duoPreauth","tags":["Duo-compatible Auth API"],"summary":"Can This User Be Asked for Approval?","description":"When result is auth, the user has an active approval phone: continue with auth. When it is enroll, the user does not use WiraPass yet or has not turned on approvals: deny access and point them to enroll_portal_url. When it is deny, the user just pressed \"Not me\" on a request from your system, so requests are paused for 15 minutes. WiraPass never answers allow here.\n\nThe devices list holds the active approval phones. Their capabilities always include auto and push, and mobile_otp only when the user has a WiraPass backup code. When no phone is signed in to WiraPass the result is still auth, but a push is refused and status_msg suggests the backup code.","security":[{"duoSignature":[]}],"parameters":[{"$ref":"#/components/parameters/DuoDate"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"username":{"type":"string","format":"email","examples":["rina@example.com"],"description":"The user's WiraPass email (case-insensitive). Send username or user_id, not both."},"user_id":{"type":"string","format":"uuid","examples":["0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d"],"description":"The WiraPass user id (UUID). Instead of username."},"ipaddr":{"type":"string","examples":["203.0.113.7"],"description":"IP address of the person signing in. On auth it is shown on the phone."},"hostname":{"type":"string","examples":["LAPTOP-RINA"],"description":"Name of the device used to sign in. On auth it is shown as the device."},"trusted_device_token":{"type":"string","description":"Accepted and ignored (WiraPass does not remember devices)."},"client_supports_verified_push":{"type":"string","description":"Accepted and ignored."}}},"example":{"username":"rina@example.com","ipaddr":"203.0.113.7","hostname":"LAPTOP-RINA"}},"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"username":{"type":"string","format":"email","examples":["rina@example.com"],"description":"The user's WiraPass email (case-insensitive). Send username or user_id, not both."},"user_id":{"type":"string","format":"uuid","examples":["0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d"],"description":"The WiraPass user id (UUID). Instead of username."},"ipaddr":{"type":"string","examples":["203.0.113.7"],"description":"IP address of the person signing in. On auth it is shown on the phone."},"hostname":{"type":"string","examples":["LAPTOP-RINA"],"description":"Name of the device used to sign in. On auth it is shown as the device."},"trusted_device_token":{"type":"string","description":"Accepted and ignored (WiraPass does not remember devices)."},"client_supports_verified_push":{"type":"string","description":"Accepted and ignored."}}}}}},"responses":{"200":{"description":"One of auth, enroll or deny.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DuoPreauthOk"},"example":{"stat":"OK","response":{"result":"auth","status_msg":"Akun aktif.","devices":[{"device":"9c8b7a6f-5e4d-4c3b-8a29-18f7e6d5c4b3","type":"phone","name":"Samsung SM-A546E","number":"","display_name":"Samsung SM-A546E","capabilities":["auto","push","mobile_otp"]}]}}}}},"400":{"$ref":"#/components/responses/DuoBadRequest"},"401":{"$ref":"#/components/responses/DuoUnauthorized"},"429":{"$ref":"#/components/responses/DuoTooManyRequests"},"503":{"$ref":"#/components/responses/DuoUnavailable"}}}},"/auth/v2/auth":{"post":{"operationId":"duoAuth","tags":["Duo-compatible Auth API"],"summary":"Ask for Approval or Check a Passcode","description":"With factor push (or auto), a request goes to every approval phone of the user that is signed in to WiraPass. Without async the call waits until the user answers or it times out (60 seconds). With async=1 it answers at once with a txid for auth_status.\n\nWith factor passcode, the server checks the 6-digit backup code from the WiraPass app. Each code works once, there are 5 tries per 5 minutes per user, and the code locks after 10 wrong tries in a row (status locked_out).\n\nThe sms and phone factors are not offered by WiraPass and are refused with 40002.","security":[{"duoSignature":[]}],"parameters":[{"$ref":"#/components/parameters/DuoDate"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"username":{"type":"string","format":"email","examples":["rina@example.com"],"description":"The user's WiraPass email (case-insensitive). Send username or user_id, not both."},"user_id":{"type":"string","format":"uuid","examples":["0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d"],"description":"The WiraPass user id (UUID). Instead of username."},"factor":{"type":"string","enum":["auto","push","passcode"],"examples":["push"],"description":"The value auto is the same as push."},"device":{"type":"string","examples":["auto"],"description":"Either auto (the default) or the id of a phone from preauth. The request still shows on every approval phone of the user."},"async":{"type":"string","enum":["0","1"],"examples":["1"],"description":"Set 1 to get a txid at once instead of waiting."},"type":{"type":"string","maxLength":120,"examples":["Masuk ke Portal Karyawan"],"description":"Title of the request on the phone. Default: \"Masuk ke <integration name>\"."},"display_username":{"type":"string","examples":["rina.putri"],"description":"Shown as the account on the phone."},"pushinfo":{"type":"string","examples":["from=Portal%20Karyawan&location=Jakarta"],"description":"URL-encoded key=value pairs, under 20000 bytes. Used: app (also from, application), account (user, username), ip (ipaddr), location, device (hostname). Other keys are ignored."},"passcode":{"type":"string","examples":["123456"],"description":"Required with factor passcode: the 6-digit backup code."},"ipaddr":{"type":"string","examples":["203.0.113.7"],"description":"IP address of the person signing in. On auth it is shown on the phone."},"hostname":{"type":"string","examples":["LAPTOP-RINA"],"description":"Name of the device used to sign in. On auth it is shown as the device."}},"required":["factor"]},"example":{"username":"rina@example.com","factor":"push","device":"auto","async":"1","type":"Masuk ke Portal Karyawan","display_username":"rina.putri","pushinfo":"from=Portal%20Karyawan&location=Jakarta","passcode":"123456","ipaddr":"203.0.113.7","hostname":"LAPTOP-RINA"}},"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"username":{"type":"string","format":"email","examples":["rina@example.com"],"description":"The user's WiraPass email (case-insensitive). Send username or user_id, not both."},"user_id":{"type":"string","format":"uuid","examples":["0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d"],"description":"The WiraPass user id (UUID). Instead of username."},"factor":{"type":"string","enum":["auto","push","passcode"],"examples":["push"],"description":"The value auto is the same as push."},"device":{"type":"string","examples":["auto"],"description":"Either auto (the default) or the id of a phone from preauth. The request still shows on every approval phone of the user."},"async":{"type":"string","enum":["0","1"],"examples":["1"],"description":"Set 1 to get a txid at once instead of waiting."},"type":{"type":"string","maxLength":120,"examples":["Masuk ke Portal Karyawan"],"description":"Title of the request on the phone. Default: \"Masuk ke <integration name>\"."},"display_username":{"type":"string","examples":["rina.putri"],"description":"Shown as the account on the phone."},"pushinfo":{"type":"string","examples":["from=Portal%20Karyawan&location=Jakarta"],"description":"URL-encoded key=value pairs, under 20000 bytes. Used: app (also from, application), account (user, username), ip (ipaddr), location, device (hostname). Other keys are ignored."},"passcode":{"type":"string","examples":["123456"],"description":"Required with factor passcode: the 6-digit backup code."},"ipaddr":{"type":"string","examples":["203.0.113.7"],"description":"IP address of the person signing in. On auth it is shown on the phone."},"hostname":{"type":"string","examples":["LAPTOP-RINA"],"description":"Name of the device used to sign in. On auth it is shown as the device."}},"required":["factor"]}}}},"responses":{"200":{"description":"Without async: the final result and status. With async: a txid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DuoAuthOk"},"example":{"stat":"OK","response":{"result":"allow","status":"allow","status_msg":"Disetujui. Anda bisa masuk."}}}}},"400":{"$ref":"#/components/responses/DuoBadRequest"},"401":{"$ref":"#/components/responses/DuoUnauthorized"},"429":{"$ref":"#/components/responses/DuoTooManyRequests"},"503":{"$ref":"#/components/responses/DuoUnavailable"}}}},"/auth/v2/auth_status":{"get":{"operationId":"duoAuthStatus","tags":["Duo-compatible Auth API"],"summary":"Status of a Request","description":"Long-poll: while pending, the server waits up to 8 seconds for a change before it answers waiting. Call again while result = waiting. Only txids of your own integration.","security":[{"duoSignature":[]}],"parameters":[{"$ref":"#/components/parameters/DuoDate"},{"name":"txid","in":"query","required":true,"description":"The txid from auth.","schema":{"type":"string","format":"uuid","examples":["3f2b8c1e-5d4a-4e6f-9a7b-1c2d3e4f5a6b"],"description":"The txid from auth."}}],"responses":{"200":{"description":"One of waiting, allow or deny, with a status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DuoAuthStatusOk"},"example":{"stat":"OK","response":{"result":"waiting","status":"pushed","status_msg":"Permintaan masuk sudah dikirim ke HP Anda. Buka WiraPass untuk menyetujui."}}}}},"400":{"$ref":"#/components/responses/DuoBadRequest"},"401":{"$ref":"#/components/responses/DuoUnauthorized"},"429":{"$ref":"#/components/responses/DuoTooManyRequests"},"503":{"$ref":"#/components/responses/DuoUnavailable"}}}},"/auth/v2/activation_qr":{"get":{"operationId":"duoActivationQr","tags":["Duo-compatible Auth API"],"summary":"QR Code of the Download Page","description":"No signature. The image activation_barcode of enroll points to.","security":[],"responses":{"200":{"description":"PNG image.","content":{"image/png":{"schema":{"type":"string","contentMediaType":"image/png"}}}}}}},"/v1/integrations/check":{"post":{"operationId":"simpleCheck","tags":["Simple JSON API"],"summary":"Can This User Be Asked for Approval?","description":"ready is true when an active approval phone is signed in to WiraPass. An unknown email answers all zeros.","security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","examples":["rina@example.com"],"description":"The user's WiraPass email."}},"required":["email"]},"example":{"email":"rina@example.com"}}}},"responses":{"200":{"description":"Readiness of the user.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleCheck"},"example":{"ready":true,"devices":2,"signedInDevices":1,"backupCode":true}}}},"400":{"description":"INVALID_EMAIL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleError"},"examples":{"INVALID_EMAIL":{"value":{"error":"INVALID_EMAIL","message":"The email is not valid."}}}}}},"401":{"description":"UNAUTHORIZED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleError"},"examples":{"UNAUTHORIZED":{"value":{"error":"UNAUTHORIZED","message":"The API key is unknown, malformed or revoked."}}}}}},"429":{"description":"RATE_LIMITED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleError"},"examples":{"RATE_LIMITED":{"value":{"error":"RATE_LIMITED","message":"A rate limit was hit; see retryAfterSec and Retry-After."}}}}}}}}},"/v1/integrations/requests":{"post":{"operationId":"simpleCreateRequest","tags":["Simple JSON API"],"summary":"Send an Approval Request","description":"Creates a request and pushes it to the user's signed-in phones.","security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","examples":["rina@example.com"],"description":"The user's WiraPass email."},"title":{"type":"string","maxLength":120,"examples":["Masuk ke VPN Kantor"],"description":"Title on the phone. Default: \"Masuk ke <integration name>\"."},"context":{"type":"object","additionalProperties":{"type":"string"},"examples":[{"app":"VPN Kantor","account":"rina","ip":"203.0.113.7","location":"Jakarta","device":"Windows laptop"}],"description":"Only app, account, ip, location, device; each at most 120 characters."},"ttlSeconds":{"type":"integer","examples":[90],"description":"Request lifetime, 30 to 300 seconds, default 60."}},"required":["email"]},"example":{"email":"rina@example.com","title":"Masuk ke VPN Kantor","context":{"app":"VPN Kantor","account":"rina","ip":"203.0.113.7","location":"Jakarta","device":"Windows laptop"},"ttlSeconds":90}}}},"responses":{"201":{"description":"Created. delivered = pushes accepted by FCM (0 is possible).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleCreated"},"example":{"id":"3f2b8c1e-5d4a-4e6f-9a7b-1c2d3e4f5a6b","expiresAt":"2026-09-27T08:17:00.000Z","delivered":1}}}},"400":{"description":"INVALID_EMAIL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleError"},"examples":{"INVALID_EMAIL":{"value":{"error":"INVALID_EMAIL","message":"The email is not valid."}}}}}},"401":{"description":"UNAUTHORIZED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleError"},"examples":{"UNAUTHORIZED":{"value":{"error":"UNAUTHORIZED","message":"The API key is unknown, malformed or revoked."}}}}}},"404":{"description":"NOT_ENROLLED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleError"},"examples":{"NOT_ENROLLED":{"value":{"error":"NOT_ENROLLED","message":"The user has no active approval phone."}}}}}},"409":{"description":"NO_SIGNED_IN_DEVICE","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleError"},"examples":{"NO_SIGNED_IN_DEVICE":{"value":{"error":"NO_SIGNED_IN_DEVICE","message":"No approval phone is signed in to WiraPass. Use a backup code."}}}}}},"429":{"description":"RATE_LIMITED, FROZEN, TOO_MANY_PENDING","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleError"},"examples":{"RATE_LIMITED":{"value":{"error":"RATE_LIMITED","message":"A rate limit was hit; see retryAfterSec and Retry-After."}},"FROZEN":{"value":{"error":"FROZEN","message":"The user pressed \"Not me\"; requests are paused until until.","until":"2026-09-27T08:30:00.000Z"}},"TOO_MANY_PENDING":{"value":{"error":"TOO_MANY_PENDING","message":"3 requests are already waiting for an answer."}}}}}}}},"get":{"operationId":"simpleRequestStatus","tags":["Simple JSON API"],"summary":"Status of a Request","description":"Only approved means yes. reported means the user pressed \"Not me\".","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"query","required":true,"description":"Request id.","schema":{"type":"string","format":"uuid","examples":["3f2b8c1e-5d4a-4e6f-9a7b-1c2d3e4f5a6b"],"description":"Request id."}},{"name":"wait","in":"query","required":false,"description":"Long-poll 0 to 25 seconds.","schema":{"type":"integer","examples":[20],"description":"Long-poll 0 to 25 seconds."}}],"responses":{"200":{"description":"Current status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleStatus"},"example":{"id":"3f2b8c1e-5d4a-4e6f-9a7b-1c2d3e4f5a6b","status":"approved","expiresAt":"2026-09-27T08:17:00.000Z","answeredAt":"2026-09-27T08:16:21.412Z","reason":null}}}},"401":{"description":"UNAUTHORIZED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleError"},"examples":{"UNAUTHORIZED":{"value":{"error":"UNAUTHORIZED","message":"The API key is unknown, malformed or revoked."}}}}}},"404":{"description":"NOT_FOUND","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleError"},"examples":{"NOT_FOUND":{"value":{"error":"NOT_FOUND","message":"Unknown request, or another integration's."}}}}}},"429":{"description":"RATE_LIMITED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleError"},"examples":{"RATE_LIMITED":{"value":{"error":"RATE_LIMITED","message":"A rate limit was hit; see retryAfterSec and Retry-After."}}}}}}}}},"/v1/integrations/cancel":{"post":{"operationId":"simpleCancel","tags":["Simple JSON API"],"summary":"Cancel a Request","description":"Cancels a pending request, for example when the user chose a backup code.","security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid","examples":["3f2b8c1e-5d4a-4e6f-9a7b-1c2d3e4f5a6b"],"description":"Request id."}},"required":["id"]},"example":{"id":"3f2b8c1e-5d4a-4e6f-9a7b-1c2d3e4f5a6b"}}}},"responses":{"200":{"description":"Status after cancelling.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleStatus"},"example":{"id":"3f2b8c1e-5d4a-4e6f-9a7b-1c2d3e4f5a6b","status":"cancelled","expiresAt":"2026-09-27T08:17:00.000Z","answeredAt":"2026-09-27T08:16:05.000Z","reason":"integration"}}}},"401":{"description":"UNAUTHORIZED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleError"},"examples":{"UNAUTHORIZED":{"value":{"error":"UNAUTHORIZED","message":"The API key is unknown, malformed or revoked."}}}}}},"404":{"description":"NOT_FOUND","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleError"},"examples":{"NOT_FOUND":{"value":{"error":"NOT_FOUND","message":"Unknown request, or another integration's."}}}}}},"429":{"description":"RATE_LIMITED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleError"},"examples":{"RATE_LIMITED":{"value":{"error":"RATE_LIMITED","message":"A rate limit was hit; see retryAfterSec and Retry-After."}}}}}}}}},"/v1/integrations/backup-code":{"post":{"operationId":"simpleBackupCode","tags":["Simple JSON API"],"summary":"Check a Backup Code","description":"For phones without internet. A wrong or used code and an unknown email get the same answer.","security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","examples":["rina@example.com"],"description":"The user's WiraPass email."},"code":{"type":"string","examples":["123456"],"description":"The 6-digit backup code."}},"required":["email","code"]},"example":{"email":"rina@example.com","code":"123456"}}}},"responses":{"200":{"description":"The code is right.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleOk"},"example":{"ok":true}}}},"401":{"description":"UNAUTHORIZED, WRONG_CODE","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleError"},"examples":{"UNAUTHORIZED":{"value":{"error":"UNAUTHORIZED","message":"The API key is unknown, malformed or revoked."}},"WRONG_CODE":{"value":{"error":"WRONG_CODE","message":"The backup code is wrong or was already used."}}}}}},"423":{"description":"CODE_LOCKED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleError"},"examples":{"CODE_LOCKED":{"value":{"error":"CODE_LOCKED","message":"The backup code is locked after 10 wrong tries; the user creates a new one on an active phone."}}}}}},"429":{"description":"RATE_LIMITED","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleError"},"examples":{"RATE_LIMITED":{"value":{"error":"RATE_LIMITED","message":"A rate limit was hit; see retryAfterSec and Retry-After."}}}}}}}}}},"components":{"securitySchemes":{"duoSignature":{"type":"http","scheme":"basic","description":"Username: the integration key (ikey). Password: the lowercase hex HMAC-SHA512 (or HMAC-SHA1 for legacy signature v2) of the canonical request, keyed with the secret key (skey). Send the same date in the Date header. Signature versions 5, 4 and 2 are accepted. See https://auth.wiracode.com/developers/signing"},"apiKey":{"type":"http","scheme":"bearer","bearerFormat":"wpk_<prefix>_<secret>","description":"The simple API key, shown once when the integration is created."}},"parameters":{"DuoDate":{"name":"Date","in":"header","required":true,"description":"RFC 2822 date, exactly the string that was signed, within 5 minutes of the server clock.","schema":{"type":"string","examples":["Tue, 21 Aug 2012 17:29:18 -0000"]}}},"responses":{"DuoBadRequest":{"description":"40001 (a required parameter is missing) or 40002 (a parameter is invalid, the user is unknown, or factor is sms or phone).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DuoFail"},"examples":{"missing":{"value":{"stat":"FAIL","code":40001,"message":"Missing required request parameters","message_detail":"username"}},"invalid":{"value":{"stat":"FAIL","code":40002,"message":"Invalid request parameters","message_detail":"factor"}}}}}},"DuoUnauthorized":{"description":"Request authentication failed: 40101 to 40106.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DuoFail"},"examples":{"e40101":{"value":{"stat":"FAIL","code":40101,"message":"Missing request credentials"}},"e40102":{"value":{"stat":"FAIL","code":40102,"message":"Invalid integration key in request credentials"}},"e40103":{"value":{"stat":"FAIL","code":40103,"message":"Invalid signature in request credentials"}},"e40104":{"value":{"stat":"FAIL","code":40104,"message":"Missing request timestamp"}},"e40105":{"value":{"stat":"FAIL","code":40105,"message":"Request timestamp is too far from the server time"}},"e40106":{"value":{"stat":"FAIL","code":40106,"message":"Invalid content type in request"}}}}}},"DuoTooManyRequests":{"description":"42901: a rate limit was hit.","headers":{"Retry-After":{"description":"Seconds to wait, when known.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DuoFail"},"example":{"stat":"FAIL","code":42901,"message":"Too many requests"}}}},"DuoUnavailable":{"description":"50301: a backend of the server is unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DuoFail"},"example":{"stat":"FAIL","code":50301,"message":"Service temporarily unavailable"}}}}},"schemas":{"DuoFail":{"type":"object","description":"Every failure of the Duo-compatible API. The HTTP status is the first three digits of code.","required":["stat","code","message"],"properties":{"stat":{"const":"FAIL"},"code":{"type":"integer","enum":[40001,40002,40101,40102,40103,40104,40105,40106,40401,40501,41301,42901,50001,50301]},"message":{"type":"string"},"message_detail":{"type":"string","description":"The parameter at fault, when there is one."}}},"DuoTimeOk":{"type":"object","description":"Server time.","required":["stat","response"],"properties":{"stat":{"const":"OK"},"response":{"type":"object","required":["time"],"properties":{"time":{"type":"integer","description":"Server time, Unix seconds."}}}}},"DuoEnrollOk":{"type":"object","description":"Activation data.","required":["stat","response"],"properties":{"stat":{"const":"OK"},"response":{"type":"object","required":["activation_barcode","activation_code","activation_url","expiration","user_id","username"],"properties":{"activation_barcode":{"type":"string","format":"uri","description":"PNG QR code of activation_url."},"activation_code":{"type":"string","description":"Opaque code for enroll_status, tied to the username."},"activation_url":{"type":"string","format":"uri","description":"The WiraPass download page."},"expiration":{"type":"integer","description":"When the activation code expires, Unix seconds."},"user_id":{"type":"string","format":"uuid","description":"The WiraPass user id, or a temporary id for enroll_status when the account does not exist yet."},"username":{"type":"string","format":"email"}}}}},"DuoEnrollStatusOk":{"type":"object","description":"Enrollment status.","required":["stat","response"],"properties":{"stat":{"const":"OK"},"response":{"type":"string","enum":["success","waiting","invalid"]}}},"DuoDevice":{"type":"object","required":["device","type","name","number","display_name","capabilities"],"properties":{"device":{"type":"string","format":"uuid","description":"WiraPass device id of an active approval phone."},"type":{"const":"phone"},"name":{"type":"string","description":"The phone label, or an empty string."},"number":{"type":"string","description":"Always empty: WiraPass knows no phone numbers."},"display_name":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string","enum":["auto","push","mobile_otp"]}}}},"DuoPreauthOk":{"type":"object","description":"What the user can do.","required":["stat","response"],"properties":{"stat":{"const":"OK"},"response":{"type":"object","required":["result","status_msg"],"properties":{"result":{"type":"string","enum":["auth","enroll","deny"]},"status_msg":{"type":"string","description":"Text for the end user (Indonesian, or English with Accept-Language: en)."},"devices":{"type":"array","items":{"$ref":"#/components/schemas/DuoDevice"},"description":"Only when result is auth."},"enroll_portal_url":{"type":"string","format":"uri","description":"Only when result is enroll: the WiraPass download page."}}}}},"DuoAuthResult":{"type":"object","required":["result","status","status_msg"],"properties":{"result":{"type":"string","enum":["allow","deny"]},"status":{"type":"string","enum":["allow","deny","fraud","timeout","locked_out"]},"status_msg":{"type":"string"}}},"DuoTxid":{"type":"object","required":["txid"],"properties":{"txid":{"type":"string","format":"uuid"}}},"DuoAuthOk":{"type":"object","description":"The final result (synchronous) or a txid (async=1).","required":["stat","response"],"properties":{"stat":{"const":"OK"},"response":{"oneOf":[{"$ref":"#/components/schemas/DuoAuthResult"},{"$ref":"#/components/schemas/DuoTxid"}]}}},"DuoAuthStatusOk":{"type":"object","description":"Current state of the transaction.","required":["stat","response"],"properties":{"stat":{"const":"OK"},"response":{"type":"object","required":["result","status","status_msg"],"properties":{"result":{"type":"string","enum":["waiting","allow","deny"]},"status":{"type":"string","enum":["pushed","allow","deny","fraud","timeout","locked_out"]},"status_msg":{"type":"string"}}}}},"SimpleError":{"type":"object","description":"Every failure of the simple API: error is the machine code.","required":["error","message"],"properties":{"error":{"type":"string","enum":["UNAUTHORIZED","INVALID_EMAIL","NOT_FOUND","NOT_ENROLLED","NO_SIGNED_IN_DEVICE","FROZEN","TOO_MANY_PENDING","RATE_LIMITED","WRONG_CODE","CODE_LOCKED","PAYLOAD_TOO_LARGE","ROUTE_NOT_FOUND","METHOD_NOT_ALLOWED","INTERNAL_ERROR","SERVICE_UNAVAILABLE"]},"message":{"type":"string"},"until":{"type":"string","format":"date-time","description":"FROZEN only."},"retryAfterSec":{"type":"integer","description":"RATE_LIMITED, when the window is known."}}},"SimpleCheck":{"type":"object","required":["ready","devices","signedInDevices","backupCode"],"properties":{"ready":{"type":"boolean"},"devices":{"type":"integer","description":"Active approval phones."},"signedInDevices":{"type":"integer","description":"Active approval phones signed in to WiraPass (only these receive pushes)."},"backupCode":{"type":"boolean","description":"The user has a backup code."}}},"SimpleCreated":{"type":"object","required":["id","expiresAt","delivered"],"properties":{"id":{"type":"string","format":"uuid"},"expiresAt":{"type":"string","format":"date-time"},"delivered":{"type":"integer"}}},"SimpleStatus":{"type":"object","required":["id","status","expiresAt","answeredAt","reason"],"properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","approved","denied","reported","expired","cancelled"]},"expiresAt":{"type":"string","format":"date-time"},"answeredAt":{"type":["string","null"],"format":"date-time"},"reason":{"type":["string","null"],"enum":["user","not_me","frozen","integration",null]}}},"SimpleOk":{"type":"object","required":["ok"],"properties":{"ok":{"const":true}}}}}}