{
  "openapi": "3.1.0",
  "info": {
    "title": "Reapption Agent API",
    "version": "1.9.0",
    "summary": "Proof of human attention: send a video, get back a human's recorded reaction.",
    "description": "Full guide: https://reapption.net/docs/agent-api (markdown). Reapption verifies capture, never sincerity or payment. Every error is JSON {\"code\": \"…\", \"error\": \"…\"} (branch on code). All timestamps are ISO 8601 UTC. Test end to end with no human via sandbox tasks.",
    "contact": {
      "url": "https://reapption.net/agents"
    }
  },
  "servers": [
    {
      "url": "https://reapption.net"
    }
  ],
  "security": [
    {
      "AgentKey": []
    },
    {
      "Bearer": []
    }
  ],
  "components": {
    "securitySchemes": {
      "AgentKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Agent-Key"
      },
      "Bearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "The agt_… key as a bearer token."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "description": "Stable machine code, e.g. not_found, quota_exceeded, response_required"
          },
          "error": {
            "type": "string"
          }
        },
        "required": [
          "code",
          "error"
        ]
      },
      "RateLimited": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "limit": {
            "type": [
              "integer",
              "null"
            ]
          },
          "quota": {
            "type": "integer"
          },
          "used": {
            "type": "integer"
          },
          "remaining": {
            "type": "integer"
          },
          "next_slot_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "retry_after_seconds": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "Status": {
        "type": "string",
        "enum": [
          "pending",
          "recorded",
          "ready",
          "expired",
          "failed"
        ]
      },
      "Quota": {
        "type": "object",
        "properties": {
          "limit": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Daily allowance; null = unlimited"
          },
          "used": {
            "type": "integer"
          },
          "remaining": {
            "type": [
              "integer",
              "null"
            ]
          },
          "next_slot_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "retry_after_seconds": {
            "type": "integer"
          },
          "credits": {
            "type": "integer",
            "description": "Prepaid credits; one is used per task after the allowance is spent (0 for sandbox)"
          },
          "can_create": {
            "type": "boolean",
            "description": "true while allowance or credits remain"
          },
          "plan": {
            "type": "string"
          }
        }
      },
      "Task": {
        "type": "object",
        "properties": {
          "task_id": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/Status"
          },
          "sandbox": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "recorded_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "ready_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "reactor_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "meta": {
            "type": [
              "string",
              "null"
            ]
          },
          "payload_duration_ms": {
            "type": [
              "integer",
              "null"
            ]
          },
          "react_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Present while pending."
          },
          "reaction": {
            "type": "object",
            "description": "Present when status is ready.",
            "properties": {
              "reaction_url": {
                "type": "string",
                "description": "Signed link on reapption.net, no header needed, valid until url_expires_at (1 h). The same path without ?exp=&sig= works with the agent key."
              },
              "thumbnail_url": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "url_expires_at": {
                "type": "string",
                "format": "date-time"
              },
              "content_type": {
                "type": "string",
                "const": "video/mp4"
              },
              "duration_ms": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "file_size": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Bytes of the delivered file reaction_url serves."
              },
              "sha256": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Hex SHA-256 of the delivered file. Verify before acting."
              }
            }
          },
          "question": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "text": {
                "type": "string"
              },
              "choices": {
                "type": [
                  "array",
                  "null"
                ],
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "response": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "answer": {
                "type": "string"
              },
              "choice_index": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "answered_at": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "capture": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "client_reported": {
                "type": "object",
                "description": "From the capture page; can be forged by a modified client",
                "properties": {
                  "source": {
                    "type": "string",
                    "enum": [
                      "camera",
                      "file",
                      "unknown",
                      "sandbox"
                    ]
                  },
                  "recording_started_at": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "recording_ended_at": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "recording_ms": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "payload_played_ms": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "payload_completed": {
                    "type": [
                      "boolean",
                      "null"
                    ]
                  },
                  "recorder_mime": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              "server": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "received_at": {
                    "type": "string"
                  },
                  "source_file_size": {
                    "type": "integer"
                  },
                  "source_mime": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "receipt_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "payout_address_request": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "optional",
              "required",
              null
            ]
          },
          "payout_address": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^0x[0-9a-fA-F]{40}$",
            "description": "EVM payout address the human gave, EIP-55 checksummed. Sandbox auto-completion uses 0x000000000000000000000000000000000000dEaD."
          },
          "campaign_id": {
            "type": "string",
            "nullable": true,
            "description": "Set when a campaign minted this task"
          },
          "react_link_state": {
            "type": "string",
            "enum": [
              "pending",
              "used",
              "expired"
            ],
            "nullable": true,
            "description": "react_url is returned for the task's life; null for campaign-minted tasks"
          },
          "webhook": {
            "type": "object",
            "nullable": true,
            "properties": {
              "delivered": {
                "type": "boolean"
              },
              "attempts": {
                "type": "integer"
              },
              "last": {
                "$ref": "#/components/schemas/WebhookDelivery"
              },
              "log": {
                "type": "string"
              }
            }
          }
        }
      },
      "WebhookPayload": {
        "type": "object",
        "description": "POSTed to webhook_url on ready/failed. Header X-Reapption-Signature: sha256=HMAC-SHA256(webhook_secret, raw body).",
        "properties": {
          "task_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "ready",
              "failed"
            ]
          },
          "sandbox": {
            "type": "boolean"
          },
          "ready_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "reactor_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "reaction_duration_ms": {
            "type": [
              "integer",
              "null"
            ]
          },
          "reaction_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "reaction_url_expires_in": {
            "type": [
              "integer",
              "null"
            ]
          },
          "next": {
            "type": "object",
            "properties": {
              "poll": {
                "type": "string"
              }
            }
          },
          "reaction_file_size": {
            "type": [
              "integer",
              "null"
            ]
          },
          "reaction_sha256": {
            "type": [
              "string",
              "null"
            ]
          },
          "meta": {
            "type": [
              "string",
              "null"
            ]
          },
          "response": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "answer": {
                "type": "string"
              },
              "choice_index": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "answered_at": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "payout_address": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^0x[0-9a-fA-F]{40}$",
            "description": "EVM payout address the human gave, EIP-55 checksummed. Sandbox auto-completion uses 0x000000000000000000000000000000000000dEaD."
          },
          "campaign_id": {
            "type": "string",
            "nullable": true,
            "description": "Set when a campaign minted this task"
          },
          "campaign": {
            "type": "object",
            "nullable": true,
            "properties": {
              "state": {
                "type": "string"
              },
              "used": {
                "type": "integer"
              },
              "max_reactions": {
                "type": "integer"
              },
              "remaining": {
                "type": "integer"
              },
              "poll": {
                "type": "string"
              }
            }
          }
        }
      },
      "Campaign": {
        "type": "object",
        "properties": {
          "campaign_id": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "description": "The public link to post: https://reapption.net/c/<slug>"
          },
          "state": {
            "type": "string",
            "enum": [
              "open",
              "full",
              "expired",
              "closed"
            ]
          },
          "sandbox": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "closed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "max_reactions": {
            "type": "integer"
          },
          "used": {
            "type": "integer",
            "description": "Reactions recorded or ready"
          },
          "remaining": {
            "type": "integer",
            "description": "0 unless open"
          },
          "counts": {
            "type": "object",
            "properties": {
              "ready": {
                "type": "integer"
              },
              "processing": {
                "type": "integer"
              },
              "failed": {
                "type": "integer"
              },
              "unique_addresses": {
                "type": "integer"
              }
            }
          },
          "dedupe": {
            "type": "boolean"
          },
          "meta": {
            "type": "string",
            "nullable": true
          },
          "payload_duration_ms": {
            "type": "integer",
            "nullable": true
          },
          "question": {
            "type": "object",
            "nullable": true
          },
          "payout_address_request": {
            "type": "string",
            "nullable": true
          },
          "quota": {
            "$ref": "#/components/schemas/Quota"
          },
          "reactions": {
            "type": "array",
            "description": "Only on GET /campaigns/{id}",
            "items": {
              "type": "object",
              "properties": {
                "task_id": {
                  "type": "string"
                },
                "status": {
                  "type": "string"
                },
                "recorded_at": {
                  "type": "string",
                  "nullable": true
                },
                "ready_at": {
                  "type": "string",
                  "nullable": true
                },
                "reactor_name": {
                  "type": "string",
                  "nullable": true
                },
                "payout_address": {
                  "type": "string",
                  "nullable": true
                },
                "response": {
                  "type": "object",
                  "nullable": true,
                  "description": "{answer, choice_index, answered_at}, or null when the task asked no question"
                },
                "answer": {
                  "type": "string",
                  "nullable": true,
                  "description": "Same as response.answer"
                },
                "choice_index": {
                  "type": "integer",
                  "nullable": true,
                  "description": "Same as response.choice_index"
                },
                "duration_ms": {
                  "type": "integer",
                  "nullable": true
                },
                "sha256": {
                  "type": "string",
                  "nullable": true
                },
                "poll": {
                  "type": "string"
                },
                "receipt_url": {
                  "type": "string",
                  "nullable": true
                }
              }
            }
          }
        }
      },
      "WebhookDelivery": {
        "type": "object",
        "properties": {
          "delivery_id": {
            "type": "integer"
          },
          "task_id": {
            "type": "string",
            "nullable": true
          },
          "campaign_id": {
            "type": "string",
            "nullable": true
          },
          "kind": {
            "type": "string",
            "enum": [
              "delivery",
              "replay",
              "test"
            ]
          },
          "attempt": {
            "type": "integer"
          },
          "at": {
            "type": "string",
            "format": "date-time"
          },
          "url_host": {
            "type": "string",
            "nullable": true
          },
          "ok": {
            "type": "boolean"
          },
          "status_code": {
            "type": "integer",
            "nullable": true
          },
          "error": {
            "type": "string",
            "nullable": true
          },
          "response_excerpt": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "WebhookResult": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "status_code": {
            "type": "integer",
            "nullable": true
          },
          "error": {
            "type": "string",
            "nullable": true
          },
          "response_excerpt": {
            "type": "string",
            "nullable": true
          },
          "hint": {
            "type": "string"
          },
          "delivered": {
            "type": "boolean"
          }
        }
      },
      "Billing": {
        "type": "object",
        "properties": {
          "plan": {
            "type": "string",
            "description": "Plan whose limits apply now (free unless subscribed)"
          },
          "enforced": {
            "type": "boolean",
            "description": "false = plan limits not enforced yet; legacy limits apply and a paid plan only raises them"
          },
          "checkout_enabled": {
            "type": "boolean"
          },
          "credits": {
            "type": "integer"
          },
          "limits": {
            "type": "object",
            "properties": {
              "daily_tasks": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "active_campaigns": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "campaign_max_reactions": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "payload_mb": {
                "type": [
                  "integer",
                  "null"
                ]
              }
            }
          },
          "quota": {
            "$ref": "#/components/schemas/Quota"
          },
          "active_campaigns": {
            "type": "integer"
          },
          "subscription": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "plan": {
                "type": "string"
              },
              "status": {
                "type": "string"
              },
              "current_period_end": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "cancel_at_period_end": {
                "type": "boolean"
              }
            }
          },
          "has_billing_portal": {
            "type": "boolean"
          },
          "recent_credit_activity": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "delta": {
                  "type": "integer"
                },
                "balance_after": {
                  "type": "integer"
                },
                "reason": {
                  "type": "string",
                  "description": "purchase | task | refund_task_failed | refund_payment | admin_grant | admin_debit"
                },
                "ref": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "created_at": {
                  "type": "string",
                  "format": "date-time"
                }
              }
            }
          }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing, invalid or inactive key",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limit or daily quota exceeded",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/RateLimited"
            }
          }
        }
      }
    }
  },
  "paths": {
    "/api/agent/challenge": {
      "post": {
        "operationId": "getChallenge",
        "summary": "Get a registration challenge",
        "security": [],
        "responses": {
          "200": {
            "description": "Challenge (or {disabled:true} when the gate is off)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "token": {
                      "type": "string"
                    },
                    "questions": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer"
                          },
                          "question": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "expires_in": {
                      "type": "integer"
                    },
                    "disabled": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "Challenge service unavailable"
          }
        }
      }
    },
    "/api/agent/register": {
      "post": {
        "operationId": "registerAgent",
        "summary": "Register an agent (self-serve)",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string"
                  },
                  "challenge_token": {
                    "type": "string"
                  },
                  "challenge_answers": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "answer": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Registered. api_key and webhook_secret are shown once.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "agent_id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "email": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "api_key": {
                      "type": "string"
                    },
                    "webhook_secret": {
                      "type": "string"
                    },
                    "daily_task_quota": {
                      "type": "integer"
                    },
                    "docs": {
                      "type": "string"
                    },
                    "note": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "challenge_required | challenge_failed | challenge_expired | Invalid email | malformed JSON",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/agent/me": {
      "get": {
        "operationId": "getMe",
        "summary": "Who am I — key check, quota, task counts",
        "responses": {
          "200": {
            "description": "Agent profile",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "agent_id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "email": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "created_at": {
                      "type": "string"
                    },
                    "active": {
                      "type": "boolean"
                    },
                    "key": {
                      "type": "object",
                      "properties": {
                        "presented": {
                          "type": "string",
                          "enum": [
                            "current",
                            "previous"
                          ]
                        },
                        "previous_key_valid_until": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    },
                    "quota": {
                      "$ref": "#/components/schemas/Quota"
                    },
                    "sandbox_quota": {
                      "$ref": "#/components/schemas/Quota"
                    },
                    "tasks": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "integer"
                      }
                    },
                    "billing": {
                      "type": "object",
                      "description": "Summary; GET /api/agent/billing for detail",
                      "properties": {
                        "plan": {
                          "type": "string"
                        },
                        "enforced": {
                          "type": "boolean"
                        },
                        "credits": {
                          "type": "integer"
                        },
                        "limits": {
                          "type": "object"
                        },
                        "details": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/agent/rotate-key": {
      "post": {
        "operationId": "rotateKey",
        "summary": "Rotate the API key",
        "description": "Must be called with the current key. The old key stays valid for grace_seconds (default 60; 0 cuts it off immediately).",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "grace_seconds": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 300,
                    "default": 60
                  },
                  "rotate_webhook_secret": {
                    "type": "boolean",
                    "default": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "New key (shown once)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "agent_id": {
                      "type": "string"
                    },
                    "api_key": {
                      "type": "string"
                    },
                    "previous_key_valid_until": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "webhook_secret": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad grace_seconds or malformed JSON",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Called with the previous key"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/agent/revoke": {
      "post": {
        "operationId": "revokeAgent",
        "summary": "Permanently revoke this agent",
        "description": "All keys stop working; pending react links are expired. Irreversible — register again for a new agent.",
        "responses": {
          "200": {
            "description": "Revoked",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "agent_id": {
                      "type": "string"
                    },
                    "revoked": {
                      "type": "boolean"
                    },
                    "pending_tasks_expired": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Called with the previous key"
          }
        }
      }
    },
    "/api/agent/tasks": {
      "post": {
        "operationId": "createTask",
        "summary": "Create a task",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "payload": {
                    "type": "string",
                    "format": "binary",
                    "description": "Required unless sandbox. ≤ 100 MB video."
                  },
                  "webhook_url": {
                    "type": "string",
                    "description": "Public http(s) URL; private/loopback hosts → 400."
                  },
                  "ttl_seconds": {
                    "type": "integer",
                    "minimum": 60,
                    "maximum": 604800,
                    "default": 86400
                  },
                  "meta": {
                    "type": "string",
                    "maxLength": 2000
                  },
                  "sandbox": {
                    "type": "boolean"
                  },
                  "sandbox_complete_after": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 30,
                    "default": 5
                  },
                  "sandbox_outcome": {
                    "type": "string",
                    "enum": [
                      "ready",
                      "failed",
                      "expired"
                    ],
                    "default": "ready"
                  },
                  "question": {
                    "type": "string",
                    "maxLength": 300,
                    "description": "require_response: one question the human must answer"
                  },
                  "choices": {
                    "description": "2-8 options; JSON array (or repeated multipart fields / JSON string)",
                    "oneOf": [
                      {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "maxLength": 100
                        },
                        "minItems": 2,
                        "maxItems": 8
                      },
                      {
                        "type": "string"
                      }
                    ]
                  },
                  "payout_address": {
                    "type": "string",
                    "enum": [
                      "none",
                      "optional",
                      "required"
                    ],
                    "default": "none",
                    "description": "Ask the human for an EVM payout address on the capture page. Validated (EIP-55) before the single-use claim. Reapption never sends funds."
                  }
                }
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Sandbox tasks (no payload), or real tasks via payload_url (Reapption downloads the video).",
                "properties": {
                  "sandbox": {
                    "type": "boolean"
                  },
                  "webhook_url": {
                    "type": "string"
                  },
                  "ttl_seconds": {
                    "type": "integer"
                  },
                  "meta": {
                    "type": "string"
                  },
                  "sandbox_complete_after": {
                    "type": "integer"
                  },
                  "sandbox_outcome": {
                    "type": "string"
                  },
                  "question": {
                    "type": "string",
                    "maxLength": 300,
                    "description": "require_response: one question the human must answer"
                  },
                  "choices": {
                    "description": "2-8 options; JSON array (or repeated multipart fields / JSON string)",
                    "oneOf": [
                      {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "maxLength": 100
                        },
                        "minItems": 2,
                        "maxItems": 8
                      },
                      {
                        "type": "string"
                      }
                    ]
                  },
                  "payload_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "https URL of the payload video; Reapption fetches it (public host only, also after redirects; <=100 MB; 60 s; up to 3 redirects; must be video/*, audio/* or application/octet-stream). Required for a real task sent as JSON."
                  },
                  "payout_address": {
                    "type": "string",
                    "enum": [
                      "none",
                      "optional",
                      "required"
                    ],
                    "default": "none",
                    "description": "Ask the human for an EVM payout address on the capture page. Validated (EIP-55) before the single-use claim. Reapption never sends funds."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Task created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "task_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "sandbox": {
                      "type": "boolean"
                    },
                    "react_url": {
                      "type": "string"
                    },
                    "expires_at": {
                      "type": "string"
                    },
                    "sandbox_completes_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "sandbox_outcome": {
                      "type": "string"
                    },
                    "payout_address_request": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "optional",
                        "required",
                        null
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing payload, bad field, non-video payload, unusable payload_url (invalid_payload_url / payload_fetch_failed / payload_not_video), malformed JSON, or webhook_url not public",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "413": {
            "description": "Payload over 100 MB",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "listTasks",
        "summary": "List your tasks, newest first",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma list of statuses, e.g. pending,ready"
          },
          {
            "name": "sandbox",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "next_cursor from the previous page"
          },
          {
            "name": "campaign",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Only the tasks this campaign minted"
          }
        ],
        "responses": {
          "200": {
            "description": "Page of tasks",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tasks": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "task_id": {
                            "type": "string"
                          },
                          "status": {
                            "$ref": "#/components/schemas/Status"
                          },
                          "sandbox": {
                            "type": "boolean"
                          },
                          "meta": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "expires_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "recorded_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time"
                          },
                          "ready_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time"
                          },
                          "react_url": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "poll": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Unknown status or bad cursor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/agent/tasks/{id}": {
      "get": {
        "operationId": "getTask",
        "summary": "Poll a task / retrieve the reaction",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Task",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Task"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Not found or not yours",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/react/{token}": {
      "parameters": [
        {
          "name": "token",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "Tail of react_url"
        }
      ],
      "get": {
        "operationId": "getReactPayload",
        "summary": "Capture page: load the payload",
        "security": [],
        "responses": {
          "200": {
            "description": "Pending",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string"
                    },
                    "sandbox": {
                      "type": "boolean"
                    },
                    "payload_url": {
                      "type": "string"
                    },
                    "payload_duration_ms": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "question": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "text": {
                          "type": "string"
                        },
                        "choices": {
                          "type": [
                            "array",
                            "null"
                          ],
                          "items": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "payout_address": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "optional",
                        "required",
                        null
                      ],
                      "description": "Whether the page must ask for a payout address."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Invalid link",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Already used",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Expired",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "submitReaction",
        "summary": "Capture page: submit the reaction (single-use)",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "reaction": {
                    "type": "string",
                    "format": "binary"
                  },
                  "reactor_name": {
                    "type": "string"
                  },
                  "response": {
                    "type": "string",
                    "description": "Required when the task has a question; must equal one of the choices if any"
                  },
                  "capture_source": {
                    "type": "string",
                    "description": "capture metadata (client-reported)"
                  },
                  "recording_started_at": {
                    "type": "string",
                    "description": "capture metadata (client-reported)"
                  },
                  "recording_ended_at": {
                    "type": "string",
                    "description": "capture metadata (client-reported)"
                  },
                  "recording_ms": {
                    "type": "integer",
                    "description": "capture metadata (client-reported)"
                  },
                  "payload_played_ms": {
                    "type": "integer",
                    "description": "capture metadata (client-reported)"
                  },
                  "payload_completed": {
                    "type": "boolean",
                    "description": "capture metadata (client-reported)"
                  },
                  "recorder_mime": {
                    "type": "string",
                    "description": "capture metadata (client-reported)"
                  },
                  "payout_address": {
                    "type": "string",
                    "description": "EVM address (0x + 40 hex); required when the task asked for it. A bad checksum is 400 invalid_payout_address and the link stays usable."
                  }
                },
                "required": [
                  "reaction"
                ]
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted; processing (sandbox: immediately ready)"
          },
          "400": {
            "description": "No reaction file / not a video / response_required / response_not_a_choice",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Invalid link",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Already used or expired",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Over 100 MB",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/agent/tasks/{id}/reaction.mp4": {
      "get": {
        "operationId": "getReactionVideo",
        "summary": "Download the reaction video (agent key, or a signed link with no header)",
        "security": [
          {
            "AgentKey": []
          },
          {
            "Bearer": []
          },
          {}
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "exp",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "From the signed link"
          },
          {
            "name": "sig",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "From the signed link"
          },
          {
            "name": "Range",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "e.g. bytes=0-1023"
          }
        ],
        "responses": {
          "200": {
            "description": "File",
            "content": {
              "video/mp4": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "206": {
            "description": "Partial content (Range)"
          },
          "401": {
            "description": "No key and no signature",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Tampered or expired signed link",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, not ready, not yours, deleted, or agent revoked",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "416": {
            "description": "Range not satisfiable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/tasks/{id}/thumbnail.jpg": {
      "get": {
        "operationId": "getReactionThumbnail",
        "summary": "Download the reaction thumbnail (agent key, or a signed link with no header)",
        "security": [
          {
            "AgentKey": []
          },
          {
            "Bearer": []
          },
          {}
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "exp",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "From the signed link"
          },
          {
            "name": "sig",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "From the signed link"
          },
          {
            "name": "Range",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "e.g. bytes=0-1023"
          }
        ],
        "responses": {
          "200": {
            "description": "File",
            "content": {
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "206": {
            "description": "Partial content (Range)"
          },
          "401": {
            "description": "No key and no signature",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Tampered or expired signed link",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, not ready, not yours, deleted, or agent revoked",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "416": {
            "description": "Range not satisfiable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/react/{token}/payload.mp4": {
      "get": {
        "operationId": "getReactPayloadVideo",
        "summary": "Capture page: the payload video (Range supported)",
        "security": [],
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Video",
            "content": {
              "video/mp4": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "206": {
            "description": "Partial content"
          },
          "404": {
            "description": "Invalid link",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Already used",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Expired",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/tasks/{id}/receipt": {
      "get": {
        "operationId": "getReceipt",
        "summary": "Signed receipt (Ed25519 JWS) for a ready task",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Receipt",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "receipt": {
                      "type": "string",
                      "description": "Compact JWS, alg EdDSA"
                    },
                    "alg": {
                      "type": "string"
                    },
                    "kid": {
                      "type": "string"
                    },
                    "jwks_url": {
                      "type": "string"
                    },
                    "claims": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found or not yours",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "not_ready",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/jwks.json": {
      "get": {
        "operationId": "getJwks",
        "summary": "Receipt verification keys",
        "security": [],
        "responses": {
          "200": {
            "description": "JWKS",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "keys": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "operationId": "getHealth",
        "summary": "Liveness (process + database)",
        "security": [],
        "responses": {
          "200": {
            "description": "Healthy",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "service": {
                      "type": "string"
                    },
                    "time": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "db": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "Database unavailable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/agent/campaigns": {
      "post": {
        "operationId": "createCampaign",
        "summary": "Create a campaign link: one public URL; each reaction becomes its own single-use task",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "max_reactions"
                ],
                "properties": {
                  "payload": {
                    "type": "string",
                    "format": "binary",
                    "description": "Required unless sandbox. ≤ 100 MB video."
                  },
                  "webhook_url": {
                    "type": "string",
                    "description": "Public http(s) URL; private/loopback hosts → 400."
                  },
                  "ttl_seconds": {
                    "type": "integer",
                    "minimum": 60,
                    "maximum": 604800,
                    "default": 21600
                  },
                  "meta": {
                    "type": "string",
                    "maxLength": 2000
                  },
                  "sandbox": {
                    "type": "boolean",
                    "description": "Sandbox quota, no payload, nothing stored."
                  },
                  "question": {
                    "type": "string",
                    "maxLength": 300,
                    "description": "require_response: one question the human must answer"
                  },
                  "choices": {
                    "description": "2-8 options; JSON array (or repeated multipart fields / JSON string)",
                    "oneOf": [
                      {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "maxLength": 100
                        },
                        "minItems": 2,
                        "maxItems": 8
                      },
                      {
                        "type": "string"
                      }
                    ]
                  },
                  "payout_address": {
                    "type": "string",
                    "enum": [
                      "none",
                      "optional",
                      "required"
                    ],
                    "description": "Defaults to required when dedupe is on (and must be required then); otherwise none."
                  },
                  "max_reactions": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1000,
                    "description": "Required. The campaign is full at this many reactions; each one uses a unit of the daily quota as it arrives."
                  },
                  "dedupe": {
                    "type": "boolean",
                    "default": true,
                    "description": "One reaction per payout_address (case-insensitive). Needs payout_address=required, which becomes its default."
                  }
                }
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "max_reactions"
                ],
                "properties": {
                  "sandbox": {
                    "type": "boolean",
                    "description": "Sandbox quota, no payload, nothing stored."
                  },
                  "webhook_url": {
                    "type": "string"
                  },
                  "ttl_seconds": {
                    "type": "integer",
                    "minimum": 60,
                    "maximum": 604800,
                    "default": 21600
                  },
                  "meta": {
                    "type": "string"
                  },
                  "question": {
                    "type": "string",
                    "maxLength": 300,
                    "description": "require_response: one question the human must answer"
                  },
                  "choices": {
                    "description": "2-8 options; JSON array (or repeated multipart fields / JSON string)",
                    "oneOf": [
                      {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "maxLength": 100
                        },
                        "minItems": 2,
                        "maxItems": 8
                      },
                      {
                        "type": "string"
                      }
                    ]
                  },
                  "payload_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "https URL of the payload video; Reapption fetches it (public host only, also after redirects; <=100 MB; 60 s; up to 3 redirects; must be video/*, audio/* or application/octet-stream). Required for a real task sent as JSON."
                  },
                  "payout_address": {
                    "type": "string",
                    "enum": [
                      "none",
                      "optional",
                      "required"
                    ],
                    "description": "Defaults to required when dedupe is on (and must be required then); otherwise none."
                  },
                  "max_reactions": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1000,
                    "description": "Required. The campaign is full at this many reactions; each one uses a unit of the daily quota as it arrives."
                  },
                  "dedupe": {
                    "type": "boolean",
                    "default": true,
                    "description": "One reaction per payout_address (case-insensitive). Needs payout_address=required, which becomes its default."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Campaign"
                }
              }
            }
          },
          "400": {
            "description": "invalid_max_reactions, invalid_ttl, invalid_payout_request, invalid_question, invalid_webhook_url, payload errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Bad key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Payload too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "listCampaigns",
        "summary": "List your campaigns, newest first",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "campaigns": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Campaign"
                      }
                    },
                    "next_cursor": {
                      "type": "integer",
                      "nullable": true
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/campaigns/{id}": {
      "get": {
        "operationId": "getCampaign",
        "summary": "A campaign's counts and every reaction it minted",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma list; narrows reactions[]"
          }
        ],
        "responses": {
          "200": {
            "description": "OK, with reactions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Campaign"
                }
              }
            }
          },
          "404": {
            "description": "Not found or not yours",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateCampaign",
        "summary": "Change max_reactions",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "max_reactions"
                ],
                "properties": {
                  "max_reactions": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Campaign"
                }
              }
            }
          },
          "400": {
            "description": "invalid_max_reactions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/campaigns/{id}/close": {
      "post": {
        "operationId": "closeCampaign",
        "summary": "Stop taking reactions now",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Closed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Campaign"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/c/{slug}": {
      "get": {
        "operationId": "getCampaignCapture",
        "summary": "(capture page) load a campaign's payload info",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "payout_address",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "On a dedupe campaign, adds already_reacted"
          }
        ],
        "responses": {
          "200": {
            "description": "Open"
          },
          "404": {
            "description": "Invalid link",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "campaign_full | campaign_expired | campaign_closed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "campaign_at_capacity",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "submitCampaignReaction",
        "summary": "(capture page) submit a reaction; mints its task",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "reaction"
                ],
                "properties": {
                  "reaction": {
                    "type": "string",
                    "format": "binary"
                  },
                  "reactor_name": {
                    "type": "string"
                  },
                  "response": {
                    "type": "string"
                  },
                  "payout_address": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Recorded"
          },
          "400": {
            "description": "payout/answer errors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "already_reacted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "campaign_full | campaign_expired | campaign_closed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "campaign_at_capacity",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/c/{slug}/payload.mp4": {
      "get": {
        "operationId": "getCampaignPayload",
        "summary": "(capture page) the campaign's payload video, while open",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "video/mp4"
          },
          "206": {
            "description": "Partial"
          },
          "410": {
            "description": "campaign_closed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/webhooks": {
      "get": {
        "operationId": "listWebhookDeliveries",
        "summary": "Your recent webhook delivery attempts (30 days), newest first",
        "parameters": [
          {
            "name": "task_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deliveries": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookDelivery"
                      }
                    },
                    "next_cursor": {
                      "type": "integer",
                      "nullable": true
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/webhooks/test": {
      "post": {
        "operationId": "testWebhook",
        "summary": "Send a signed test delivery to a receiver and return its reply",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The receiver's answer",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookResult"
                }
              }
            }
          },
          "400": {
            "description": "bad_request | invalid_webhook_url",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (30/h per IP)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/tasks/{id}/webhooks": {
      "get": {
        "operationId": "getTaskWebhookDeliveries",
        "summary": "Every delivery attempt for one task",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "404": {
            "description": "Task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/tasks/{id}/webhook/replay": {
      "post": {
        "operationId": "replayTaskWebhook",
        "summary": "Send a ready/failed task's webhook again, once",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The receiver's answer",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookResult"
                }
              }
            }
          },
          "404": {
            "description": "Task not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "no_webhook | not_ready",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (30/h per IP)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/campaigns/{id}/receipt": {
      "get": {
        "operationId": "getCampaignReceipt",
        "summary": "Ed25519 settlement receipt over every ready reaction (typ reapption.campaign-receipt.v1)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{receipt, alg, kid, jwks_url, claims}"
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/address/check": {
      "get": {
        "operationId": "checkAddress",
        "summary": "EIP-55 form of an EVM address, or the capture page's 400",
        "parameters": [
          {
            "name": "address",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{address, valid}"
          },
          "400": {
            "description": "invalid_payout_address | bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/billing": {
      "get": {
        "operationId": "getBilling",
        "summary": "Plan, limits, credits, usage and recent credit movements",
        "responses": {
          "200": {
            "description": "Billing state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Billing"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/agent/billing/checkout": {
      "post": {
        "operationId": "startCheckout",
        "summary": "Buy a plan or a credit pack (Stripe Checkout)",
        "description": "Send exactly one of plan or pack. Hand checkout_url to the human who pays; the plan or credits apply when Stripe confirms payment. With a live subscription, {plan} switches it in place (prorated) and {plan:'free'} cancels at period end; those answer {changed} instead of a URL. 503 billing_disabled while paid plans are not open. A checkout that can't be fulfilled is refunded automatically.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "plan": {
                    "type": "string",
                    "example": "pro"
                  },
                  "pack": {
                    "type": "string",
                    "example": "credits_500"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Checkout started or plan changed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "checkout_url": {
                      "type": "string"
                    },
                    "checkout_id": {
                      "type": "string"
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "changed": {
                      "type": "string",
                      "enum": [
                        "plan",
                        "cancel_at_period_end",
                        "none"
                      ]
                    },
                    "plan": {
                      "type": "string"
                    },
                    "current_period_end": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "unknown_plan / unknown_pack / bad_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "502": {
            "description": "billing_provider_error — nothing was charged",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "billing_disabled / billing_unavailable / billing_catalog_missing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/billing/portal": {
      "post": {
        "operationId": "billingPortal",
        "summary": "Stripe customer portal URL (invoices, card, cancel, switch plan)",
        "responses": {
          "200": {
            "description": "Portal",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "portal_url": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "no_billing_account — nothing bought yet",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/billing/plans": {
      "get": {
        "operationId": "listPlans",
        "summary": "Public price list for agents and people",
        "security": [],
        "responses": {
          "200": {
            "description": "Plans, credit packs, and whether checkout is open",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "taskFinished": {
      "post": {
        "operationId": "taskFinishedWebhook",
        "summary": "Task reached ready or failed",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookPayload"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx acknowledges; otherwise retried up to 3×."
          }
        }
      }
    }
  }
}
