{
  "openapi": "3.1.0",
  "info": {
    "title": "ShotSold seller agent API",
    "version": "0.1.0",
    "summary": "Draft listings for a ShotSold seller. The token only creates and reads drafts; the seller publishes.",
    "description": "An AI agent drafts listings on behalf of one seller, using a token that seller created in Settings under Assistant access. Creating a draft returns publishUrl, a single-use link for the seller. It only publishes when the seller is signed in and confirms; it works once and expires after 7 days. Agents should hand it to the seller and not open it. The same draft tools are available over MCP at /mcp. See /llms.txt.",
    "contact": {
      "email": "legal@shotsold.ai"
    }
  },
  "externalDocs": {
    "description": "llms.txt",
    "url": "https://shotsold.ai/llms.txt"
  },
  "servers": [
    {
      "url": "https://shotsold.ai"
    }
  ],
  "security": [
    {
      "sellerToken": []
    }
  ],
  "components": {
    "securitySchemes": {
      "sellerToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "Seller API token (starts with ssat_). The seller creates it while signed in, in Settings under Assistant access; it is shown once. It only works on the endpoints in this spec; any other route answers 401 TOKEN_SCOPE."
      }
    },
    "schemas": {
      "CreateDraft": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "title",
          "description",
          "ask",
          "category"
        ],
        "properties": {
          "title": {
            "type": "string",
            "maxLength": 200
          },
          "description": {
            "type": "string",
            "maxLength": 8000
          },
          "ask": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "The price in US dollars (seller-net). The floor is derived from it and strategy."
          },
          "strategy": {
            "type": "string",
            "enum": [
              "hold",
              "balanced",
              "fast"
            ],
            "description": "How far the AI Negotiator may come down from ask. hold: up to 5% off. balanced (default): up to 10% off. fast: up to 25% off. At $10 or less offers below ask are never accepted."
          },
          "category": {
            "type": "string",
            "maxLength": 80,
            "description": "A ShotSold category id, e.g. furniture/sofas-and-armchairs. Full list in /llms-full.txt."
          },
          "conditionClaim": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "NEW",
              "EXCELLENT",
              "GOOD",
              "USED",
              "DAMAGED",
              null
            ]
          },
          "mediaIds": {
            "type": "array",
            "maxItems": 6,
            "items": {
              "type": "string"
            },
            "description": "Ids from POST /api/agent/media. At least one photo per listing."
          },
          "locationHint": {
            "type": "string",
            "maxLength": 200,
            "description": "Accepted and ignored. Pickup location is the one on the seller account."
          }
        }
      },
      "Draft": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "const": "DRAFT"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "ask": {
            "type": "number"
          },
          "bounds": {
            "type": "object",
            "properties": {
              "ask": {
                "type": "number"
              },
              "target": {
                "type": "number"
              },
              "floor": {
                "type": "number"
              }
            }
          },
          "mediaIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sellerUrl": {
            "type": "string",
            "format": "uri"
          },
          "sellingUrl": {
            "type": "string",
            "format": "uri"
          },
          "publishUrl": {
            "type": "string",
            "format": "uri",
            "description": "Single-use publish link for the seller. Publishes only when the seller is signed in and confirms. Give it to the seller; do not open it."
          },
          "publishExpiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "note": {
            "type": "string"
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "violations": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "field": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "responses": {
      "Error": {
        "description": "Refused, with a reason.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  },
  "paths": {
    "/api/agent/listings": {
      "post": {
        "operationId": "createDraftListing",
        "summary": "Create one draft listing",
        "description": "JSON with mediaIds, or multipart/form-data with the same fields plus photo files in file or files. Remote image URLs, suggestedAsk, target, floor and bounds are refused; send ask and optionally strategy. The seller account needs a pickup location in the service area.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateDraft"
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "title",
                  "description",
                  "ask",
                  "category"
                ],
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "ask": {
                    "type": "string",
                    "description": "Dollars, more than 0."
                  },
                  "strategy": {
                    "type": "string",
                    "enum": [
                      "hold",
                      "balanced",
                      "fast"
                    ],
                    "description": "How far the AI Negotiator may come down from ask. hold: up to 5% off. balanced (default): up to 10% off. fast: up to 25% off. At $10 or less offers below ask are never accepted."
                  },
                  "category": {
                    "type": "string"
                  },
                  "conditionClaim": {
                    "type": "string"
                  },
                  "mediaIds": {
                    "type": "string",
                    "description": "Comma-separated, or repeat the field."
                  },
                  "file": {
                    "type": "array",
                    "maxItems": 6,
                    "items": {
                      "type": "string",
                      "contentMediaType": "application/octet-stream"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Draft created. Not live.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Draft"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "413": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/agent/media": {
      "post": {
        "operationId": "uploadMedia",
        "summary": "Upload photo or clip files, get media ids",
        "description": "Up to 6 files in file or files. JPEG, PNG, WebP or GIF up to 10 MB; MP4, MOV or WebM up to 100 MB.",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "array",
                    "maxItems": 6,
                    "items": {
                      "type": "string",
                      "contentMediaType": "application/octet-stream"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Stored.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "media": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "contentType": {
                            "type": "string"
                          },
                          "byteSize": {
                            "type": "integer"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "413": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/agent/listings/{id}": {
      "get": {
        "operationId": "getListing",
        "summary": "Read one of this seller's listings",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Status and links.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "ask": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "mediaCount": {
                      "type": "integer"
                    },
                    "missing": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Fields still needed before it can be published."
                    },
                    "sellerUrl": {
                      "type": "string",
                      "format": "uri"
                    },
                    "sellingUrl": {
                      "type": "string",
                      "format": "uri"
                    },
                    "publicUrl": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uri"
                    },
                    "publishLinkOutstanding": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/agent/whoami": {
      "get": {
        "operationId": "whoami",
        "summary": "Which seller the token belongs to",
        "responses": {
          "200": {
            "description": "The seller.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "displayName": {
                      "type": "string"
                    },
                    "email": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/mcp": {
      "post": {
        "operationId": "mcp",
        "summary": "MCP server (Streamable HTTP, stateless JSON-RPC)",
        "description": "Protocol 2025-03-26. initialize and tools/list work without a token; tools/call needs the seller token. Tools: whoami, upload_media (base64 bytes), create_draft_listing, get_listing. No publish tool.",
        "security": [
          {},
          {
            "sellerToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "jsonrpc",
                  "method"
                ],
                "properties": {
                  "jsonrpc": {
                    "const": "2.0"
                  },
                  "id": {},
                  "method": {
                    "type": "string"
                  },
                  "params": {
                    "type": "object"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response."
          },
          "202": {
            "description": "Notification accepted."
          }
        }
      }
    }
  }
}
