{
  "openapi": "3.1.0",
  "info": {
    "title": "Product Rally for agents",
    "version": "1.0.0",
    "description": "Read the Product Rally board, and pitch one product for a human to review. Reading is open. The pitch does not publish anything and never grants votes, membership or a ranking. Bulk sending gets the sender blocked.",
    "contact": {
      "url": "https://productrally.com/agents"
    },
    "license": {
      "name": "Public data, attribution appreciated"
    }
  },
  "servers": [
    {
      "url": "https://productrally.com"
    }
  ],
  "paths": {
    "/api/agent/products": {
      "get": {
        "operationId": "search_products",
        "summary": "Search Product Rally",
        "description": "Search the products on Product Rally by words, category or pricing. Returns ranked matches with their vote counts and pages. With no query it returns the newest hunts.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Words to match against name, domain, tagline and the hunter's note.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "Category slug, e.g. dev-tools.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "pricing",
            "in": "query",
            "required": false,
            "description": "One of free, freemium, paid, onetime.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1 to 50, default 10.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "202": {
            "description": "Pitch accepted and queued for a human to read"
          },
          "400": {
            "description": "The payload needs fixing"
          },
          "404": {
            "description": "Not found"
          },
          "409": {
            "description": "Already on the board or already queued"
          },
          "429": {
            "description": "A limit was hit. Stop, do not retry in a loop"
          }
        }
      }
    },
    "/api/agent/products/{slug}": {
      "get": {
        "operationId": "get_product",
        "summary": "Read one product",
        "description": "Read one published product by its slug: the tagline, the hunter's note, the category, tags, pricing, votes and its weekly place.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The slug from a product page URL, e.g. linear.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "202": {
            "description": "Pitch accepted and queued for a human to read"
          },
          "400": {
            "description": "The payload needs fixing"
          },
          "404": {
            "description": "Not found"
          },
          "409": {
            "description": "Already on the board or already queued"
          },
          "429": {
            "description": "A limit was hit. Stop, do not retry in a loop"
          }
        }
      }
    },
    "/api/agent/categories": {
      "get": {
        "operationId": "list_categories",
        "summary": "List categories",
        "description": "List every category with how many products it holds. Use a slug from here as the category filter in search_products.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success"
          },
          "202": {
            "description": "Pitch accepted and queued for a human to read"
          },
          "400": {
            "description": "The payload needs fixing"
          },
          "404": {
            "description": "Not found"
          },
          "409": {
            "description": "Already on the board or already queued"
          },
          "429": {
            "description": "A limit was hit. Stop, do not retry in a loop"
          }
        }
      }
    },
    "/api/agent/submit": {
      "post": {
        "operationId": "submit_product",
        "summary": "Pitch a product for review",
        "description": "Pitch a product to be launched on Product Rally. Send it once, for a product you or the person you work for actually built or genuinely uses. A human reads every pitch and replies by email. This does not publish anything and it never grants votes, membership or a ranking. Bulk sending gets the sender blocked.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success"
          },
          "202": {
            "description": "Pitch accepted and queued for a human to read"
          },
          "400": {
            "description": "The payload needs fixing"
          },
          "404": {
            "description": "Not found"
          },
          "409": {
            "description": "Already on the board or already queued"
          },
          "429": {
            "description": "A limit was hit. Stop, do not retry in a loop"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "description": "The product's own https URL."
                  },
                  "email": {
                    "type": "string",
                    "description": "A real address the reviewer can reply to. An address at the product's own domain is read first."
                  },
                  "name": {
                    "type": "string",
                    "description": "The product name. Defaults to the domain."
                  },
                  "tagline": {
                    "type": "string",
                    "description": "One line, up to 140 characters, saying what it does."
                  },
                  "pitch": {
                    "type": "string",
                    "description": "Up to 600 characters on why it belongs here and who it is for. Written by a person, not padded."
                  },
                  "submitterName": {
                    "type": "string",
                    "description": "Who is pitching."
                  },
                  "agent": {
                    "type": "string",
                    "description": "The agent doing the sending, e.g. claude-code. Self-declared and unverified, but honest is better."
                  }
                },
                "required": [
                  "url",
                  "email"
                ]
              }
            }
          }
        }
      }
    },
    "/api/agent/status/{id}": {
      "get": {
        "operationId": "check_submission",
        "summary": "Check a pitch",
        "description": "Check what happened to a pitch, using the id that submit_product returned. Statuses are pending, approved and rejected.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id returned by submit_product.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "202": {
            "description": "Pitch accepted and queued for a human to read"
          },
          "400": {
            "description": "The payload needs fixing"
          },
          "404": {
            "description": "Not found"
          },
          "409": {
            "description": "Already on the board or already queued"
          },
          "429": {
            "description": "A limit was hit. Stop, do not retry in a loop"
          }
        }
      }
    }
  }
}