openapi: 3.1.0 info: title: WRNexus Managed CAPTCHA API version: 0.5.0 servers: - url: https://captcha.example.com paths: /v1/challenges: post: summary: Create a public challenge requestBody: required: true content: application/json: schema: type: object required: [siteKey, action] properties: siteKey: { type: string } action: { type: string } type: { type: string, enum: [ number, alpha, alphanumeric, calculation, image, honeypot, timing, not-robot, ], } presentation: { type: string, enum: [visual, audio, invisible] } difficulty: { type: string, enum: [easy, normal, hard] } disturbance: { type: integer, minimum: 25, maximum: 75, default: 50 } imageStyle: type: string default: random enum: [ random, classic, collision, snow, corrosion, spiderweb, cross-shadow, split, split2, cut, darts, distortion, stitch, striped, wave, grid-noise, scribble, pixel, broken-lines, ] allowedStyles: oneOf: - { type: string, description: Comma-separated renderer names } - type: array items: { type: string } excludedStyles: oneOf: - { type: string, description: Comma-separated renderer names } - type: array items: { type: string } randomizeStyle: { type: boolean, default: false } locale: { type: string } responses: "201": description: Challenge created content: application/json: schema: { $ref: "#/components/schemas/Challenge" } "403": { description: Invalid site key or hostname } "429": { description: Project quota exceeded } /v1/solve: post: summary: Solve a public challenge and receive a temporary response token requestBody: required: true content: application/json: schema: type: object required: [challengeId, action] properties: challengeId: { type: string } action: { type: string } answer: { oneOf: [{ type: string }, { type: number }] } selections: { type: array, items: { type: string } } honeypot: { type: string } timingToken: { type: string } responses: "200": description: Challenge solved content: application/json: schema: { $ref: "#/components/schemas/Verification" } "400": { description: Challenge rejected } /v1/verify: post: summary: Verify and consume a response token on the application server security: [{ projectSecret: [] }] requestBody: required: true content: application/json: schema: type: object required: [responseToken, action] properties: responseToken: { type: string } action: { type: string } hostname: { type: string } sessionId: { type: string } responses: "200": description: Token accepted and consumed content: application/json: schema: { $ref: "#/components/schemas/Verification" } "400": { description: Token invalid, expired, mismatched, or already used } "401": { description: Invalid project secret } /v1/projects: get: summary: List projects security: [{ adminToken: [] }] responses: "200": { description: Project list without secrets } post: summary: Create a project and return keys once security: [{ adminToken: [] }] requestBody: required: true content: application/json: schema: type: object required: [name] properties: name: { type: string } allowedHostnames: { type: array, items: { type: string } } monthlyQuota: { type: integer, minimum: 1 } responses: "201": { description: Project and initial keys } /v1/projects/{id}/rotate-secret: post: summary: Rotate a project verification secret security: [{ adminToken: [] }] parameters: - in: path name: id required: true schema: { type: string } responses: "200": { description: New secret returned once } "404": { description: Project not found } components: securitySchemes: projectSecret: type: http scheme: bearer adminToken: type: http scheme: bearer schemas: Challenge: type: object required: [id, provider, type, presentation, action, prompt, createdAt, expiresAt, responseField] properties: id: { type: string } provider: { type: string } type: { type: string } presentation: { type: string } action: { type: string } prompt: { type: string } image: { type: string } audioUrl: { type: string } items: { type: array, items: { type: object } } createdAt: { type: integer } expiresAt: { type: integer } responseField: { type: string } metadata: type: object properties: difficulty: { type: string } disturbance: { type: integer } imageStyle: { type: string } requestedImageStyle: { type: string } imageStylePool: type: array items: { type: string } Verification: type: object required: [success, provider, action] properties: success: { type: boolean } provider: { type: string } action: { type: string } code: { type: string } message: { type: string } responseToken: { type: string } expiresAt: { type: integer }