{
  "openapi": "3.1.0",
  "info": {
    "title": "plagio API",
    "version": "1.0.0",
    "summary": "The real design of any website, captured in a real browser.",
    "description": "Capture a component, a whole page, a site's brand tokens or its animations as a specification a coding agent can rebuild from. Get a key from Dashboard, Settings on https://plagio.dev.",
    "contact": {
      "name": "plagio support",
      "email": "support@plagio.dev",
      "url": "https://docs.plagio.dev/api"
    }
  },
  "servers": [
    {
      "url": "https://plagio.dev/v1"
    }
  ],
  "externalDocs": {
    "description": "REST API docs",
    "url": "https://docs.plagio.dev/api"
  },
  "security": [
    {
      "apiKey": []
    }
  ],
  "tags": [
    {
      "name": "Captures"
    },
    {
      "name": "Account"
    }
  ],
  "paths": {
    "/component": {
      "post": {
        "operationId": "captureComponent",
        "summary": "Capture a component",
        "description": "One component from a page as a prompt to rebuild from: HTML and CSS snapshot, design tokens, structure, states and a screenshot. Costs 10 credits, 5 from cache.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Page the component lives on"
                  },
                  "describe": {
                    "type": "string",
                    "description": "The component in plain words, such as \"pricing card\". Send this or selector"
                  },
                  "selector": {
                    "type": "string",
                    "description": "CSS selector, when you already know it"
                  },
                  "stack": {
                    "type": "string",
                    "enum": [
                      "react-tailwind",
                      "react-css",
                      "vue",
                      "html"
                    ],
                    "default": "react-tailwind",
                    "description": "The stack the prompt targets"
                  },
                  "viewport": {
                    "type": "object",
                    "description": "Browser size for the capture",
                    "properties": {
                      "width": {
                        "type": "integer",
                        "example": 1280
                      },
                      "height": {
                        "type": "integer",
                        "example": 900
                      }
                    }
                  },
                  "fidelity": {
                    "type": "string",
                    "enum": [
                      "responsive",
                      "pixel"
                    ],
                    "default": "responsive"
                  },
                  "motion": {
                    "type": "boolean",
                    "default": true,
                    "description": "Also capture animation"
                  },
                  "format": {
                    "type": "string",
                    "enum": [
                      "compact",
                      "full"
                    ],
                    "default": "compact"
                  },
                  "fresh": {
                    "type": "boolean",
                    "default": false,
                    "description": "Skip the cache and render the page again"
                  },
                  "async": {
                    "type": "boolean",
                    "description": "Answer 202 with a job id instead of waiting"
                  },
                  "webhook": {
                    "type": "string",
                    "format": "uri",
                    "description": "URL that gets a POST when the job ends. Implies async"
                  }
                }
              },
              "example": {
                "url": "https://linear.app/pricing",
                "describe": "pricing card",
                "stack": "react-tailwind"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The capture: JSON by default, or written for an agent with `Accept: text/markdown`",
            "headers": {
              "x-units-charged": {
                "description": "Credits this call cost",
                "schema": {
                  "type": "integer"
                }
              },
              "x-units-remaining": {
                "description": "The month's credits left",
                "schema": {
                  "type": "integer"
                }
              },
              "x-ratelimit-remaining": {
                "description": "Calls left this minute",
                "schema": {
                  "type": "integer"
                }
              },
              "x-cache": {
                "description": "HIT, MISS or REVALIDATED",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "202": {
            "description": "Accepted as a job, when `async` or `webhook` is sent. Poll `GET /jobs/{id}`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          }
        },
        "tags": [
          "Captures"
        ]
      }
    },
    "/page": {
      "post": {
        "operationId": "capturePage",
        "summary": "Capture a page",
        "description": "A whole page mapped into sections. Costs 10 credits, 5 from cache.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "The page to capture"
                  },
                  "stack": {
                    "type": "string",
                    "enum": [
                      "react-tailwind",
                      "react-css",
                      "vue",
                      "html"
                    ],
                    "default": "react-tailwind",
                    "description": "The stack the prompt targets"
                  },
                  "viewport": {
                    "type": "object",
                    "description": "Browser size for the capture",
                    "properties": {
                      "width": {
                        "type": "integer",
                        "example": 1280
                      },
                      "height": {
                        "type": "integer",
                        "example": 900
                      }
                    }
                  },
                  "format": {
                    "type": "string",
                    "enum": [
                      "compact",
                      "full"
                    ],
                    "default": "compact"
                  },
                  "fresh": {
                    "type": "boolean",
                    "default": false,
                    "description": "Skip the cache and render the page again"
                  },
                  "async": {
                    "type": "boolean",
                    "description": "Answer 202 with a job id instead of waiting"
                  },
                  "webhook": {
                    "type": "string",
                    "format": "uri",
                    "description": "URL that gets a POST when the job ends. Implies async"
                  }
                }
              },
              "example": {
                "url": "https://linear.app"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The capture: JSON by default, or written for an agent with `Accept: text/markdown`",
            "headers": {
              "x-units-charged": {
                "description": "Credits this call cost",
                "schema": {
                  "type": "integer"
                }
              },
              "x-units-remaining": {
                "description": "The month's credits left",
                "schema": {
                  "type": "integer"
                }
              },
              "x-ratelimit-remaining": {
                "description": "Calls left this minute",
                "schema": {
                  "type": "integer"
                }
              },
              "x-cache": {
                "description": "HIT, MISS or REVALIDATED",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "202": {
            "description": "Accepted as a job, when `async` or `webhook` is sent. Poll `GET /jobs/{id}`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          }
        },
        "tags": [
          "Captures"
        ]
      }
    },
    "/brand": {
      "post": {
        "operationId": "extractBrand",
        "summary": "Extract a brand",
        "description": "A site's design language as a Tailwind theme, CSS variables, W3C design tokens and a DESIGN.md. Costs 10 credits per page read.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "The page to start from; the homepage is usually best"
                  },
                  "pages": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 5,
                    "default": 2,
                    "description": "How many pages of the same site to read. Ignored when paths is given"
                  },
                  "paths": {
                    "type": "array",
                    "maxItems": 4,
                    "items": {
                      "type": "string"
                    },
                    "description": "Specific pages of the same site to read after url"
                  },
                  "darkMode": {
                    "type": "boolean",
                    "default": false,
                    "description": "Also read the site's dark theme"
                  },
                  "format": {
                    "type": "string",
                    "enum": [
                      "summary",
                      "full"
                    ],
                    "default": "summary"
                  },
                  "fresh": {
                    "type": "boolean",
                    "default": false,
                    "description": "Skip the cache and render the page again"
                  },
                  "async": {
                    "type": "boolean",
                    "description": "Answer 202 with a job id instead of waiting"
                  },
                  "webhook": {
                    "type": "string",
                    "format": "uri",
                    "description": "URL that gets a POST when the job ends. Implies async"
                  }
                }
              },
              "example": {
                "url": "https://stripe.com",
                "pages": 2
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The capture: JSON by default, or written for an agent with `Accept: text/markdown`",
            "headers": {
              "x-units-charged": {
                "description": "Credits this call cost",
                "schema": {
                  "type": "integer"
                }
              },
              "x-units-remaining": {
                "description": "The month's credits left",
                "schema": {
                  "type": "integer"
                }
              },
              "x-ratelimit-remaining": {
                "description": "Calls left this minute",
                "schema": {
                  "type": "integer"
                }
              },
              "x-cache": {
                "description": "HIT, MISS or REVALIDATED",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "202": {
            "description": "Accepted as a job, when `async` or `webhook` is sent. Poll `GET /jobs/{id}`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          }
        },
        "tags": [
          "Captures"
        ]
      }
    },
    "/motion": {
      "post": {
        "operationId": "extractMotion",
        "summary": "Extract motion",
        "description": "Keyframes with timing and easing, scroll entrances and draggable elements. Costs 10 credits, 5 from cache.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "The page to read"
                  },
                  "describe": {
                    "type": "string",
                    "description": "The element, in plain words"
                  },
                  "selector": {
                    "type": "string",
                    "description": "CSS selector, when you already know it"
                  },
                  "fresh": {
                    "type": "boolean",
                    "default": false,
                    "description": "Skip the cache and render the page again"
                  },
                  "async": {
                    "type": "boolean",
                    "description": "Answer 202 with a job id instead of waiting"
                  },
                  "webhook": {
                    "type": "string",
                    "format": "uri",
                    "description": "URL that gets a POST when the job ends. Implies async"
                  }
                }
              },
              "example": {
                "url": "https://linear.app"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The capture: JSON by default, or written for an agent with `Accept: text/markdown`",
            "headers": {
              "x-units-charged": {
                "description": "Credits this call cost",
                "schema": {
                  "type": "integer"
                }
              },
              "x-units-remaining": {
                "description": "The month's credits left",
                "schema": {
                  "type": "integer"
                }
              },
              "x-ratelimit-remaining": {
                "description": "Calls left this minute",
                "schema": {
                  "type": "integer"
                }
              },
              "x-cache": {
                "description": "HIT, MISS or REVALIDATED",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "202": {
            "description": "Accepted as a job, when `async` or `webhook` is sent. Poll `GET /jobs/{id}`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          }
        },
        "tags": [
          "Captures"
        ]
      }
    },
    "/usage": {
      "get": {
        "operationId": "getUsage",
        "summary": "Check usage",
        "description": "The credits and limits of the key's account. Free.",
        "tags": [
          "Account"
        ],
        "responses": {
          "200": {
            "description": "Current usage",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/jobs/{id}": {
      "get": {
        "operationId": "getJob",
        "summary": "Read a job",
        "description": "The state and result of an async capture.",
        "tags": [
          "Captures"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The job",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "string"
          },
          "requestId": {
            "type": "string"
          }
        }
      }
    },
    "responses": {
      "Error": {
        "description": "The request was refused",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}
