{
  "openapi": "3.1.0",
  "info": {
    "title": "WorldSignals API",
    "version": "1.0.0",
    "description": "Real-world events (new companies, tenders, insolvencies, funding rounds, vulnerabilities, outages and more) as standard CloudEvents with typed payloads, plus sign-up for AI agents acting for a person, signed webhooks and the member's account. Reading events needs the free access key that comes with signing up. The remote MCP server (/mcp) and its OAuth 2.1 sign-in are described at https://worldsignals.dev/docs#mcp.",
    "contact": {
      "name": "Yumankind OÜ",
      "email": "hello@worldsignals.dev",
      "url": "https://worldsignals.dev/docs"
    },
    "termsOfService": "https://worldsignals.dev/terms"
  },
  "servers": [
    {
      "url": "https://worldsignals.dev"
    }
  ],
  "externalDocs": {
    "url": "https://worldsignals.dev/docs"
  },
  "paths": {
    "/api/events": {
      "get": {
        "operationId": "listEvents",
        "summary": "Collected public events: after an event number (oldest first, for polling), or newest first",
        "parameters": [
          {
            "name": "types",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated event types, e.g. company.registered,company.dissolved"
          },
          {
            "name": "country",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "One ISO 3166-1 alpha-2 code, or several comma-separated, e.g. ES or ES,FR: events in any of them. XX is the events about the whole world (vulnerabilities, outages, AI models): add it to get them too, e.g. ES,FR,XX. Without country, every country, XX included."
          },
          {
            "name": "after",
            "in": "query",
            "schema": {
              "type": "string",
              "examples": [
                "51234",
                "2026-10-23T09:00:00Z"
              ]
            },
            "description": "An event number: the events stored after it, oldest first, for polling (send the `cursor` of the last answer). Or an RFC 3339 time: the newest events first seen after it."
          },
          {
            "name": "before",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "Newest first, paging back: the `before` of the last answer gives the next older page, while `older` is true. Not with an event number as `after`."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "platforms",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated operating systems in data.platforms: windows, macos, linux, android, ios, chromeos, freebsd."
          },
          {
            "name": "ecosystems",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated package registries in data.ecosystems (languages): npm, pypi, maven, nuget, go, rubygems, crates, packagist, pub, hex, swift, cocoapods, github_actions, wordpress."
          },
          {
            "name": "tags",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated tags; the event has any of them."
          },
          {
            "name": "where",
            "in": "query",
            "required": false,
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "maxItems": 8,
              "items": {
                "type": "string"
              }
            },
            "description": "A field condition, <path><op><value>, ops = != > >= < <=, | between values for = and !=. Repeat for more; all must hold. Example: data.cvss.base_score>=9."
          }
        ],
        "responses": {
          "200": {
            "description": "Events and the total kept (30 days)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "events": {
                      "type": "array",
                      "items": {
                        "$ref": "https://worldsignals.dev/schemas/envelope/1.json"
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "cursor": {
                      "type": "integer",
                      "description": "Send it as `after` next time: the newest event in the answer, or, when the answer isn't full, the newest event stored, so the next poll skips what didn't match."
                    },
                    "more": {
                      "type": "boolean",
                      "description": "With an event number in `after`: more events are waiting; ask again now."
                    },
                    "before": {
                      "type": [
                        "integer",
                        "null"
                      ],
                      "description": "Unless `after` is an event number: send it as `before` to get the next older page, newest first. Null when there is nothing older."
                    },
                    "older": {
                      "type": "boolean",
                      "description": "Unless `after` is an event number: older matches may remain; ask again with `before`."
                    },
                    "hint": {
                      "type": "string",
                      "description": "When `country` leaves out XX but some of `types` only come as events about the whole world: which, and to add XX."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No valid access key; see /api/events/preview",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "preview": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A filter isn't valid; the error says why."
          },
          "429": {
            "description": "This access key is over its limit for the minute (120 requests shared by /api/events, /mcp, /api/me and webhook tests; 12 new /api/stream connections). Wait for Retry-After seconds."
          }
        },
        "security": [
          {
            "accessKey": []
          }
        ]
      }
    },
    "/api/stats": {
      "get": {
        "operationId": "getStats",
        "summary": "What collection has produced",
        "responses": {
          "200": {
            "description": "Counts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "events": {
                      "type": "integer",
                      "description": "Public events kept (30 days)."
                    },
                    "sources": {
                      "type": "integer",
                      "description": "Sources those events came from."
                    },
                    "countries": {
                      "type": "integer",
                      "description": "Countries those events are in."
                    },
                    "types": {
                      "type": "integer",
                      "description": "Event types among them."
                    },
                    "latest": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time",
                      "description": "When the latest of them happened."
                    },
                    "connectors": {
                      "type": "integer",
                      "description": "Connectors defined."
                    },
                    "eventTypes": {
                      "type": "integer",
                      "description": "Event types defined in the catalogue."
                    },
                    "retentionDays": {
                      "type": "integer",
                      "description": "Days public events are kept."
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/catalogue": {
      "get": {
        "operationId": "getCatalogue",
        "summary": "Every event type with an example event and the connectors that collect it today",
        "responses": {
          "200": {
            "description": "Families of event types, deliveries, speeds, countries, and the personal mail domains that wait for the daily approval",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "families": {
                      "type": "array"
                    },
                    "deliveries": {
                      "type": "array"
                    },
                    "speeds": {
                      "type": "array"
                    },
                    "countries": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "worldwide": {
                      "type": "string",
                      "description": "WW: every country, the same as no list"
                    },
                    "personalDomains": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Personal mailbox domains (Gmail, Outlook…): sign-ups from them wait for our daily approval; other domains with mail servers get in as they confirm."
                    },
                    "visitorCountry": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/signup-requests": {
      "post": {
        "operationId": "requestSignup",
        "summary": "An agent asks to sign up its person or company",
        "description": "Nothing is signed up until the person opens confirm_url and confirms. Pass message_for_human to them. At most 10 requests a day from one network address.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "agent"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "Your person's email address."
                  },
                  "company": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "role": {
                    "type": "string",
                    "maxLength": 100
                  },
                  "useCase": {
                    "type": "string",
                    "maxLength": 1000,
                    "description": "What the events are for."
                  },
                  "other": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "A source or event type that isn't in the catalogue yet."
                  },
                  "eventTypes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Event type ids from GET /api/catalogue, e.g. company.registered. Optional: the person can choose later."
                  },
                  "countries": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "ISO 3166-1 alpha-2 codes; empty or [\"WW\"] means every country, XX included. XX is the events about the whole world (vulnerabilities, outages, AI models): add it to a list to get them too, e.g. [\"ES\", \"XX\"]."
                  },
                  "delivery": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "cli",
                        "webhook",
                        "agent",
                        "folder",
                        "mcp",
                        "stream",
                        "email",
                        "slack",
                        "bulk"
                      ]
                    }
                  },
                  "speed": {
                    "type": "string",
                    "enum": [
                      "realtime",
                      "hourly",
                      "daily"
                    ]
                  },
                  "plan": {
                    "type": "string",
                    "enum": [
                      "free",
                      "developer",
                      "team",
                      "business",
                      "enterprise"
                    ]
                  },
                  "billing": {
                    "type": "string",
                    "enum": [
                      "monthly",
                      "yearly"
                    ]
                  },
                  "agent": {
                    "type": "object",
                    "required": [
                      "name"
                    ],
                    "properties": {
                      "name": {
                        "type": "string",
                        "maxLength": 80
                      },
                      "version": {
                        "type": "string",
                        "maxLength": 40
                      },
                      "url": {
                        "type": "string",
                        "maxLength": 300
                      }
                    }
                  },
                  "pickup_public_key": {
                    "type": "string",
                    "pattern": "^[A-Za-z0-9_-]{43}$",
                    "description": "Optional: an Ed25519 public key (32 raw bytes, base64url). Then no pickup_secret is returned, and GET /api/signup-requests/{token} gives the key to a request signed with X-WS-Timestamp (unix seconds, within 5 minutes) and X-WS-Signature (base64url signature of \"GET /api/signup-requests/<token> <timestamp>\")."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Pending until the person confirms",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "const": "pending"
                    },
                    "email": {
                      "type": "string"
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "confirm_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "hint": {
                      "type": "string",
                      "description": "When countries leave out XX but some of eventTypes only come as events about the whole world: which, and to add XX."
                    },
                    "message_for_human": {
                      "type": "string"
                    },
                    "pickup_secret": {
                      "type": "string",
                      "description": "For the agent only: send it as `Authorization: Bearer <pickup_secret>` to GET /api/signup-requests/{token} to fetch the key, once. Never show it to the person or put it in a URL. Shown only in this answer. Not returned when the request sent pickup_public_key."
                    },
                    "key_pickup": {
                      "type": "string",
                      "description": "How to fetch the key, in words."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing email or agent.name, or a value we don't know: the error names the unknown event types, countries, delivery, speed, plan or billing (see /api/catalogue). Country codes may be lower case.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many requests from this address today",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/signup-requests/{token}": {
      "get": {
        "operationId": "getSignupRequest",
        "summary": "What was asked, for the confirmation page; once the person is in, the access key",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "description": "The code from `confirm_url` (its `t` parameter).",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{43}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The request, or just its status once decided or expired",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "pending",
                        "confirmed",
                        "declined",
                        "expired"
                      ]
                    },
                    "email": {
                      "type": "string",
                      "description": "While pending."
                    },
                    "agent": {
                      "type": "object",
                      "description": "While pending: the agent's name, version and url."
                    },
                    "eventTypes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "While pending."
                    },
                    "countries": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "While pending."
                    },
                    "delivery": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "While pending."
                    },
                    "speed": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "While pending."
                    },
                    "company": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "role": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "useCase": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "plan": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "billing": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "email_confirmed": {
                      "type": "boolean",
                      "description": "Once confirmed: whether the person clicked the email we sent them."
                    },
                    "waiting_list": {
                      "type": "boolean",
                      "description": "True while the person waits for our daily approval."
                    },
                    "access_key": {
                      "type": "string",
                      "description": "The key that reads events for the person, once they are in."
                    },
                    "error": {
                      "type": "string",
                      "description": "With status expired: why, and what to do."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unknown link",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "description": "While pending: the request (email, agent, choices). Once the person confirmed and clicked the email we sent them, and is in (at once for a work email, after our daily approval for a personal address; until then `email_confirmed: false`, then `waiting_list: true`): `access_key`, only with the header `Authorization: Bearer <pickup_secret>` from the request's answer, and only once. The token is in URLs, so on its own it shows the status only. The key can be fetched for 7 days after the person is in; then the status is `expired`."
      }
    },
    "/api/signup-requests/{token}/confirm": {
      "post": {
        "operationId": "confirmSignupRequest",
        "summary": "The person confirms (used by /confirm)",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "description": "The code from `confirm_url` (its `t` parameter).",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{43}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Confirmed. We email the person a link that names the agent; only a click on it lets the agent fetch the key. Pressing confirm again before that click sends the email again (resent: true), until 24 hours after the first confirmation; nothing is saved again.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "status": {
                      "const": "confirmed"
                    },
                    "email": {
                      "type": "string",
                      "enum": [
                        "sent",
                        "not_sent"
                      ],
                      "description": "Whether that email went out."
                    },
                    "emailError": {
                      "type": "string"
                    },
                    "resent": {
                      "type": "boolean",
                      "description": "This press sent the email again."
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Already declined, or the person already clicked the email: nothing more to send",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "410": {
            "description": "Expired: the link ran out, or 24 hours passed since the person confirmed without clicking the email",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/signup-requests/{token}/decline": {
      "post": {
        "operationId": "declineSignupRequest",
        "summary": "The person declines; the email is forgotten",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "description": "The code from `confirm_url` (its `t` parameter).",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{43}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Declined",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "status": {
                      "const": "declined"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Already decided",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/subscribe": {
      "post": {
        "operationId": "subscribe",
        "summary": "A person signs up on the site (email alone, or with choices)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "Your person's email address."
                  },
                  "company": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "role": {
                    "type": "string",
                    "maxLength": 100
                  },
                  "useCase": {
                    "type": "string",
                    "maxLength": 1000,
                    "description": "What the events are for."
                  },
                  "other": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "A source or event type that isn't in the catalogue yet."
                  },
                  "eventTypes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Event type ids from GET /api/catalogue, e.g. company.registered. Optional: the person can choose later."
                  },
                  "countries": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "ISO 3166-1 alpha-2 codes; empty or [\"WW\"] means worldwide."
                  },
                  "delivery": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "cli",
                        "webhook",
                        "agent",
                        "folder",
                        "mcp",
                        "stream",
                        "email",
                        "slack",
                        "bulk"
                      ]
                    }
                  },
                  "speed": {
                    "type": "string",
                    "enum": [
                      "realtime",
                      "hourly",
                      "daily"
                    ]
                  },
                  "plan": {
                    "type": "string",
                    "enum": [
                      "free",
                      "developer",
                      "team",
                      "business",
                      "enterprise"
                    ]
                  },
                  "billing": {
                    "type": "string",
                    "enum": [
                      "monthly",
                      "yearly"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved. status check_email: a link is on its way, or one went to this address in the last 15 minutes and still works (so no second one), or a per-address or new-address limit held it back (the answer doesn't say, so it can't tell who is a member). status email_not_sent with emailError: it couldn't be sent (the per-network daily limit, or email is down). status saved: a signed-in member saved their own stream; nothing to confirm.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "check_email",
                        "email_not_sent",
                        "saved"
                      ]
                    },
                    "emailError": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/schemas/{name}/1.json": {
      "get": {
        "operationId": "getSchema",
        "summary": "JSON Schema for the envelope, shared definitions or a typed payload",
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "envelope, common, or an event type such as company.registered"
          }
        ],
        "responses": {
          "200": {
            "description": "The schema",
            "content": {
              "application/schema+json": {}
            }
          },
          "404": {
            "description": "No such schema",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/events/preview": {
      "get": {
        "operationId": "previewEvents",
        "summary": "Without a key: the newest events reduced to type, country, place and day",
        "responses": {
          "200": {
            "description": "Preview",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string"
                          },
                          "country": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "place": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "day": {
                            "type": "string",
                            "format": "date"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/verify": {
      "post": {
        "operationId": "verifyEmail",
        "summary": "Confirm the email (or sign in) with the token from an emailed link, used by /verify",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "t"
                ],
                "properties": {
                  "t": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The link is used and the email confirmed. With status approved, this browser is signed in (the ws_access cookie) and the key is in the answer; with status waiting, the person waits for our daily approval (work emails get in at once).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "email": {
                      "type": "string"
                    },
                    "purpose": {
                      "type": "string",
                      "enum": [
                        "confirm",
                        "sign_in",
                        "approved",
                        "agent",
                        "new_key",
                        "changes"
                      ],
                      "description": "What the link was for."
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "approved",
                        "waiting"
                      ]
                    },
                    "accessKey": {
                      "type": "string",
                      "description": "With status approved. After a new_key link it is the new key, and the old one no longer works."
                    },
                    "next": {
                      "type": "string",
                      "description": "Back to an app's connect page (/oauth/authorize?…): only in the browser that asked for the link."
                    },
                    "keyShown": {
                      "type": "string",
                      "enum": [
                        "agent",
                        "before_agent",
                        "before"
                      ],
                      "description": "With status approved and no accessKey: why. agent: the AI agent that asked for this sign-up fetches the key, once. before_agent: an agent asked, but the key was already shown, so it can't. before: it was shown when it was made."
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "Link already used",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "email": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "410": {
            "description": "Link expired, or the sign-up no longer exists",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "email": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/sign-in": {
      "post": {
        "operationId": "signIn",
        "summary": "Email a sign-in link (or the confirmation again). Same answer whether or not the email is signed up",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Check your inbox. The same answer for every address, signed up or not, limits or not."
          },
          "429": {
            "description": "Too many requests from this network this minute"
          }
        }
      }
    },
    "/api/sign-out": {
      "post": {
        "operationId": "signOut",
        "summary": "Clear this browser's sign-in cookie",
        "responses": {
          "200": {
            "description": "Signed out"
          }
        }
      }
    },
    "/api/stream": {
      "get": {
        "operationId": "streamEvents",
        "summary": "Collected public events, live, as Server-Sent Events",
        "description": "Each event is sent as `id: <event number>` and `data: <standard event JSON>`. Reconnect with `Last-Event-ID` (or `after`) to carry on after that number; without one the stream starts at the newest event. Named messages: `ready` ({after, head, types, countries}), `gap` ({after, oldest}: events after your number are past the 30 days kept), `bye` ({after}: reconnect now). An `id:` line with no data means nothing up to that number matched: keep it as your place. A `: hb` comment comes every 15 seconds and means everything waiting has been sent. One connection lasts a few minutes. Defaults to the types and countries chosen at sign-up. XX is the events about the whole world (vulnerabilities, outages, AI models), a country like the others; `ready` has a `hint` when the countries leave out XX but a type only comes as XX.",
        "security": [
          {
            "accessKey": []
          }
        ],
        "parameters": [
          {
            "name": "Last-Event-ID",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^\\d+$"
            },
            "description": "The number of the last event you have."
          },
          {
            "name": "after",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^\\d+$"
            },
            "description": "Same as Last-Event-ID, for clients that can't set headers."
          },
          {
            "name": "types",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated event types, or `all`. Default: the sign-up's choice."
          },
          {
            "name": "countries",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated ISO 3166-1 alpha-2 codes, or `all`. Default: the sign-up's choice. XX is the events about the whole world: add it to get them too, e.g. ES,XX."
          },
          {
            "name": "platforms",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated operating systems in data.platforms: windows, macos, linux, android, ios, chromeos, freebsd."
          },
          {
            "name": "ecosystems",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated package registries in data.ecosystems (languages): npm, pypi, maven, nuget, go, rubygems, crates, packagist, pub, hex, swift, cocoapods, github_actions, wordpress."
          },
          {
            "name": "tags",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated tags; the event has any of them."
          },
          {
            "name": "where",
            "in": "query",
            "required": false,
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "maxItems": 8,
              "items": {
                "type": "string"
              }
            },
            "description": "A field condition, <path><op><value>, ops = != > >= < <=, | between values for = and !=. Repeat for more; all must hold. Example: data.cvss.base_score>=9."
          }
        ],
        "responses": {
          "200": {
            "description": "The event stream.",
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "No known event type in `types`, or a cursor that isn't an event number."
          },
          "401": {
            "description": "No valid access key."
          },
          "429": {
            "description": "This access key is over its limit for the minute (120 requests shared by /api/events, /mcp, /api/me and webhook tests; 12 new /api/stream connections). Wait for Retry-After seconds."
          }
        }
      }
    },
    "/api/unsubscribe": {
      "post": {
        "operationId": "leave",
        "summary": "Stop emails or delete a sign-up, from the signed link in every email",
        "description": "The link in each email's footer opens /unsubscribe, whose buttons post {u, t, action} here; action is unsubscribe (stop emails, keep the access key) or delete (remove the sign-up and its data; the key stops working). Mail apps' one-click unsubscribe posts List-Unsubscribe=One-Click to the same address with u and t in the query.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "u",
                  "t",
                  "action"
                ],
                "properties": {
                  "u": {
                    "type": "string"
                  },
                  "t": {
                    "type": "string"
                  },
                  "action": {
                    "enum": [
                      "unsubscribe",
                      "delete"
                    ]
                  }
                }
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "properties": {
                  "List-Unsubscribe": {
                    "const": "One-Click"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ok, status: unsubscribed | deleted}"
          },
          "403": {
            "description": "The link isn't signed for that sign-up."
          }
        }
      }
    },
    "/api/webhooks": {
      "post": {
        "operationId": "createWebhook",
        "summary": "Register an https endpoint: each matching event is POSTed to it, signed per Standard Webhooks",
        "description": "One event per request, in the order events were collected, as CloudEvents structured JSON. Answer 2xx within 10 seconds; failures are retried after 1, 5 and 30 minutes, 2 hours, then every 12 hours, and after three days (or at once on 410) the webhook is switched off until you enable it. Up to 5 per access key. See https://worldsignals.dev/docs#webhooks for verifying signatures.",
        "security": [
          {
            "accessKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "An https address."
                  },
                  "types": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Event type ids, or [\"all\"]. Left out: the types saved for your stream."
                  },
                  "countries": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "ISO codes, or [\"all\"]. Left out: the countries saved for your stream. XX is the events about the whole world (vulnerabilities, outages, AI models): add it to get them too."
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "platforms": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "ecosystems": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "where": {
                    "type": "array",
                    "maxItems": 8,
                    "items": {
                      "type": "string"
                    },
                    "description": "Field conditions, as for /api/events, e.g. data.cvss.base_score>=9."
                  },
                  "after": {
                    "type": "integer",
                    "description": "Start after this event number instead of with the next event collected."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created. The secret is shown only here.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "wh_…"
                        },
                        "url": {
                          "type": "string",
                          "format": "uri"
                        },
                        "filter": {
                          "type": "object",
                          "properties": {
                            "types": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              },
                              "description": "Event type ids, or [\"all\"]. Left out: the types saved for your stream."
                            },
                            "countries": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              },
                              "description": "ISO codes, or [\"all\"]. Left out: the countries saved for your stream. Global events (XX) come with any country."
                            },
                            "tags": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "platforms": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "ecosystems": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "where": {
                              "type": "array",
                              "maxItems": 8,
                              "items": {
                                "type": "string"
                              },
                              "description": "Field conditions, as for /api/events, e.g. data.cvss.base_score>=9."
                            }
                          }
                        },
                        "cursor": {
                          "type": "integer",
                          "description": "The last event number delivered (or skipped as not matching)."
                        },
                        "enabled": {
                          "type": "boolean"
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "delivered": {
                          "type": "integer"
                        },
                        "last_attempt_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "last_success_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "last_status": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        },
                        "last_error": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Our own short description, never the endpoint's answer."
                        },
                        "failures": {
                          "type": "integer"
                        },
                        "next_attempt_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "disabled_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "disabled_reason": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    },
                    {
                      "type": "object",
                      "properties": {
                        "secret": {
                          "type": "string",
                          "description": "whsec_…"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "The url or a filter isn't valid; the error says why.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No valid access key."
          },
          "409": {
            "description": "Already 5 webhooks for this access key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "listWebhooks",
        "summary": "Your webhooks",
        "security": [
          {
            "accessKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "The webhooks and how many you may have",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "webhooks": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "description": "wh_…"
                          },
                          "url": {
                            "type": "string",
                            "format": "uri"
                          },
                          "filter": {
                            "type": "object",
                            "properties": {
                              "types": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                },
                                "description": "Event type ids, or [\"all\"]. Left out: the types saved for your stream."
                              },
                              "countries": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                },
                                "description": "ISO codes, or [\"all\"]. Left out: the countries saved for your stream. Global events (XX) come with any country."
                              },
                              "tags": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              },
                              "platforms": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              },
                              "ecosystems": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              },
                              "where": {
                                "type": "array",
                                "maxItems": 8,
                                "items": {
                                  "type": "string"
                                },
                                "description": "Field conditions, as for /api/events, e.g. data.cvss.base_score>=9."
                              }
                            }
                          },
                          "cursor": {
                            "type": "integer",
                            "description": "The last event number delivered (or skipped as not matching)."
                          },
                          "enabled": {
                            "type": "boolean"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "delivered": {
                            "type": "integer"
                          },
                          "last_attempt_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time"
                          },
                          "last_success_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time"
                          },
                          "last_status": {
                            "type": [
                              "integer",
                              "null"
                            ]
                          },
                          "last_error": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Our own short description, never the endpoint's answer."
                          },
                          "failures": {
                            "type": "integer"
                          },
                          "next_attempt_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time"
                          },
                          "disabled_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time"
                          },
                          "disabled_reason": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        }
                      }
                    },
                    "max": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No valid access key."
          }
        }
      }
    },
    "/api/webhooks/{id}": {
      "get": {
        "operationId": "getWebhook",
        "summary": "One webhook and its delivery state",
        "security": [
          {
            "accessKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The webhook's id, wh_…"
          }
        ],
        "responses": {
          "200": {
            "description": "The webhook",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "webhook": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "wh_…"
                        },
                        "url": {
                          "type": "string",
                          "format": "uri"
                        },
                        "filter": {
                          "type": "object",
                          "properties": {
                            "types": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              },
                              "description": "Event type ids, or [\"all\"]. Left out: the types saved for your stream."
                            },
                            "countries": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              },
                              "description": "ISO codes, or [\"all\"]. Left out: the countries saved for your stream. Global events (XX) come with any country."
                            },
                            "tags": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "platforms": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "ecosystems": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "where": {
                              "type": "array",
                              "maxItems": 8,
                              "items": {
                                "type": "string"
                              },
                              "description": "Field conditions, as for /api/events, e.g. data.cvss.base_score>=9."
                            }
                          }
                        },
                        "cursor": {
                          "type": "integer",
                          "description": "The last event number delivered (or skipped as not matching)."
                        },
                        "enabled": {
                          "type": "boolean"
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "delivered": {
                          "type": "integer"
                        },
                        "last_attempt_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "last_success_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "last_status": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        },
                        "last_error": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Our own short description, never the endpoint's answer."
                        },
                        "failures": {
                          "type": "integer"
                        },
                        "next_attempt_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "disabled_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "disabled_reason": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such webhook.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteWebhook",
        "summary": "Remove a webhook",
        "security": [
          {
            "accessKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The webhook's id, wh_…"
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such webhook.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/webhooks/{id}/test": {
      "post": {
        "operationId": "testWebhook",
        "summary": "Send a signed webhook.test event now",
        "security": [
          {
            "accessKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The webhook's id, wh_…"
          }
        ],
        "responses": {
          "200": {
            "description": "How the endpoint answered",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "description": "The test event's id."
                    },
                    "ok": {
                      "type": "boolean"
                    },
                    "status": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "error": {
                      "type": "string",
                      "description": "Our own short description of what went wrong."
                    },
                    "ms": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such webhook.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "This access key is over its limit for the minute (120 requests shared by /api/events, /mcp, /api/me and webhook tests; 12 new /api/stream connections). Wait for Retry-After seconds."
          }
        }
      }
    },
    "/api/webhooks/{id}/rotate-secret": {
      "post": {
        "operationId": "rotateWebhookSecret",
        "summary": "A new signing secret; requests are signed with both for a day",
        "security": [
          {
            "accessKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The webhook's id, wh_…"
          }
        ],
        "responses": {
          "200": {
            "description": "The new secret",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "secret": {
                      "type": "string"
                    },
                    "previous_secret_valid_until": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such webhook.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/webhooks/{id}/enable": {
      "post": {
        "operationId": "enableWebhook",
        "summary": "Switch a webhook back on; delivery resumes where it stopped (within the 30 days kept)",
        "security": [
          {
            "accessKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The webhook's id, wh_…"
          }
        ],
        "responses": {
          "200": {
            "description": "The webhook",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "webhook": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "wh_…"
                        },
                        "url": {
                          "type": "string",
                          "format": "uri"
                        },
                        "filter": {
                          "type": "object",
                          "properties": {
                            "types": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              },
                              "description": "Event type ids, or [\"all\"]. Left out: the types saved for your stream."
                            },
                            "countries": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              },
                              "description": "ISO codes, or [\"all\"]. Left out: the countries saved for your stream. Global events (XX) come with any country."
                            },
                            "tags": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "platforms": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "ecosystems": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "where": {
                              "type": "array",
                              "maxItems": 8,
                              "items": {
                                "type": "string"
                              },
                              "description": "Field conditions, as for /api/events, e.g. data.cvss.base_score>=9."
                            }
                          }
                        },
                        "cursor": {
                          "type": "integer",
                          "description": "The last event number delivered (or skipped as not matching)."
                        },
                        "enabled": {
                          "type": "boolean"
                        },
                        "created_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "delivered": {
                          "type": "integer"
                        },
                        "last_attempt_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "last_success_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "last_status": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        },
                        "last_error": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Our own short description, never the endpoint's answer."
                        },
                        "failures": {
                          "type": "integer"
                        },
                        "next_attempt_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "disabled_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "disabled_reason": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such webhook.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/me": {
      "get": {
        "operationId": "getMe",
        "summary": "The signed-in member: email and saved stream; the access key with ?key=1",
        "security": [
          {
            "accessKey": []
          }
        ],
        "parameters": [
          {
            "name": "key",
            "in": "query",
            "required": false,
            "schema": {
              "const": "1"
            },
            "description": "Include the access key."
          }
        ],
        "responses": {
          "200": {
            "description": "The member",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "email": {
                      "type": "string"
                    },
                    "eventTypes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "countries": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "accessKey": {
                      "type": "string",
                      "description": "Only with ?key=1."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "This access key is over its limit for the minute (120 requests shared by /api/events, /mcp, /api/me and webhook tests; 12 new /api/stream connections). Wait for Retry-After seconds."
          }
        }
      }
    },
    "/api/rules": {
      "get": {
        "summary": "Your combined rules, with their status",
        "security": [
          {
            "accessKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "The rules"
          }
        }
      },
      "post": {
        "summary": "A new combined rule: a private signal when both steps happen to the same company within within_days",
        "security": [
          {
            "accessKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "first",
                  "then",
                  "within_days"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 80
                  },
                  "first": {
                    "type": "object",
                    "properties": {
                      "types": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        },
                        "maxItems": 4
                      },
                      "countries": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      },
                      "tags": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      },
                      "where": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        },
                        "maxItems": 10
                      }
                    },
                    "required": [
                      "types"
                    ]
                  },
                  "then": {
                    "type": "object",
                    "properties": {
                      "types": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        },
                        "maxItems": 4
                      },
                      "countries": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      },
                      "tags": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      },
                      "where": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        },
                        "maxItems": 10
                      }
                    },
                    "required": [
                      "types"
                    ]
                  },
                  "within_days": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 365
                  },
                  "order": {
                    "enum": [
                      "first_then",
                      "any"
                    ]
                  },
                  "watchlist": {
                    "type": "string"
                  },
                  "enabled": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Saved"
          },
          "400": {
            "description": "Not a valid rule"
          },
          "402": {
            "description": "Premium: the Team plan and up, while paid"
          },
          "409": {
            "description": "Your plan's number of rules is used"
          }
        }
      }
    },
    "/api/rules/test": {
      "post": {
        "summary": "Dry-run an unsaved rule over the events kept (30 days); nothing is stored",
        "security": [
          {
            "accessKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "first",
                  "then",
                  "within_days"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 80
                  },
                  "first": {
                    "type": "object",
                    "properties": {
                      "types": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        },
                        "maxItems": 4
                      },
                      "countries": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      },
                      "tags": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      },
                      "where": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        },
                        "maxItems": 10
                      }
                    },
                    "required": [
                      "types"
                    ]
                  },
                  "then": {
                    "type": "object",
                    "properties": {
                      "types": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        },
                        "maxItems": 4
                      },
                      "countries": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      },
                      "tags": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      },
                      "where": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        },
                        "maxItems": 10
                      }
                    },
                    "required": [
                      "types"
                    ]
                  },
                  "within_days": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 365
                  },
                  "order": {
                    "enum": [
                      "first_then",
                      "any"
                    ]
                  },
                  "watchlist": {
                    "type": "string"
                  },
                  "enabled": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What it would have matched"
          },
          "402": {
            "description": "Premium: the Team plan and up, while paid"
          }
        }
      }
    },
    "/api/rules/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "summary": "One rule",
        "security": [
          {
            "accessKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "The rule"
          },
          "404": {
            "description": "Not yours, or no such rule"
          }
        }
      },
      "patch": {
        "summary": "Change or pause a rule",
        "security": [
          {
            "accessKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Changed"
          },
          "402": {
            "description": "Premium: the Team plan and up, while paid"
          }
        }
      },
      "delete": {
        "summary": "Remove a rule",
        "security": [
          {
            "accessKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Removed"
          }
        }
      }
    },
    "/api/rules/{id}/test": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "summary": "Dry-run a saved rule over the events kept (30 days)",
        "security": [
          {
            "accessKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "What it would have matched"
          },
          "402": {
            "description": "Premium: the Team plan and up, while paid"
          }
        }
      }
    },
    "/api/billing/checkout": {
      "post": {
        "summary": "A Stripe payment link for a paid plan, to open in a browser",
        "security": [
          {
            "accessKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "plan"
                ],
                "properties": {
                  "plan": {
                    "enum": [
                      "developer",
                      "team",
                      "business"
                    ]
                  },
                  "billing": {
                    "enum": [
                      "monthly",
                      "yearly"
                    ],
                    "default": "monthly"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "url: the Stripe Checkout page"
          },
          "400": {
            "description": "Unknown plan or billing period"
          },
          "503": {
            "description": "Paid plans aren't open yet"
          }
        }
      }
    },
    "/api/billing/portal": {
      "post": {
        "summary": "A link to Stripe's customer portal: plan, card, invoices, cancel",
        "security": [
          {
            "accessKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "url: the portal"
          },
          "404": {
            "description": "No paid plan to manage yet"
          },
          "503": {
            "description": "Paid plans aren't open yet"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "accessKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "wsk_… key. POST /api/verify answers accessKey (and sets the ws_access cookie) once the person is in: work emails get in as soon as they confirm; personal addresses (Gmail, Outlook…) after our daily approval. An agent gets access_key from GET /api/signup-requests/{token} once its person confirmed the request and clicked the email we sent them, and is in. Each key is shown once; the Account page (/console/account) replaces it with a new one."
      }
    }
  }
}
