{
  "openapi": "3.0.3",
  "info": {
    "title": "Comic Timelines API",
    "version": "1.1.0",
    "description": "Classic public-domain comics and the new endings people have written for them, panel by panel, with links to write your own on Comic Timelines. Signed-in users can also make things on Comic Timelines with their own credits: always call quote first, read the price out, and only call make after the user says yes.",
    "contact": {
      "email": "hello@askmizan.com"
    }
  },
  "servers": [
    {
      "url": "https://storyconnectors.pages.dev/comics/v1"
    }
  ],
  "paths": {
    "/comics": {
      "get": {
        "operationId": "listComics",
        "summary": "The showroom comics",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/comics/{id}": {
      "get": {
        "operationId": "getComic",
        "summary": "One comic and its public endings",
        "responses": {
          "200": {
            "description": "OK"
          },
          "404": {
            "description": "Unknown comic"
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "planetmars"
          }
        ]
      }
    },
    "/endings": {
      "get": {
        "operationId": "listEndings",
        "summary": "Every public new ending, with what changes",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/endings/{comic}/{id}": {
      "get": {
        "operationId": "getEnding",
        "summary": "Read one ending panel by panel: scene, caption and dialogue",
        "responses": {
          "200": {
            "description": "OK"
          },
          "404": {
            "description": "Unknown ending"
          }
        },
        "parameters": [
          {
            "name": "comic",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "planetmars"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "elza01"
          }
        ]
      }
    },
    "/health": {
      "get": {
        "operationId": "health",
        "summary": "Service status",
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/me": {
      "get": {
        "operationId": "myAccount",
        "summary": "The user's Comic Timelines credits, daily limit and credit packs",
        "security": [
          {
            "museSignIn": [
              "credits:read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Plain-sentence error, safe to read out",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/quote": {
      "post": {
        "operationId": "quote",
        "summary": "Price a make without spending anything. Read the returned say to the user and ask them to confirm.",
        "security": [
          {
            "museSignIn": [
              "credits:read"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "kind",
                  "comic"
                ],
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "ending",
                      "move_panel",
                      "move_version"
                    ],
                    "description": "ending: a new ending from a panel (sketch free, ink 10 credits a page). move_panel: bring one panel of the user's own version to life (18 credits). move_version: every panel not yet moving (18 each)."
                  },
                  "comic": {
                    "type": "string",
                    "description": "Comic id from /comics, or one of the user's own uploads."
                  },
                  "version": {
                    "type": "string",
                    "description": "The user's version id (move_panel, move_version)."
                  },
                  "page": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "panel": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "change": {
                    "type": "string",
                    "description": "ending: what happens instead."
                  },
                  "pages": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 3
                  },
                  "tier": {
                    "type": "string",
                    "enum": [
                      "sketch",
                      "ink"
                    ]
                  },
                  "title": {
                    "type": "string"
                  },
                  "look": {
                    "type": "string",
                    "enum": [
                      "cinematic",
                      "gentle"
                    ],
                    "description": "Moving pictures: cinematic (more action, the default) or gentle (calmer). Same price."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Plain-sentence error, safe to read out",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Plain-sentence error, safe to read out",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/make": {
      "post": {
        "operationId": "make",
        "summary": "Make it with the user's own credits. Only after the user has agreed to the quoted price; send that price as credits.",
        "security": [
          {
            "museSignIn": [
              "credits:read",
              "make"
            ]
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 80
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "type": "object",
                    "required": [
                      "kind",
                      "comic"
                    ],
                    "properties": {
                      "kind": {
                        "type": "string",
                        "enum": [
                          "ending",
                          "move_panel",
                          "move_version"
                        ],
                        "description": "ending: a new ending from a panel (sketch free, ink 10 credits a page). move_panel: bring one panel of the user's own version to life (18 credits). move_version: every panel not yet moving (18 each)."
                      },
                      "comic": {
                        "type": "string",
                        "description": "Comic id from /comics, or one of the user's own uploads."
                      },
                      "version": {
                        "type": "string",
                        "description": "The user's version id (move_panel, move_version)."
                      },
                      "page": {
                        "type": "integer",
                        "minimum": 1
                      },
                      "panel": {
                        "type": "integer",
                        "minimum": 1
                      },
                      "change": {
                        "type": "string",
                        "description": "ending: what happens instead."
                      },
                      "pages": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 3
                      },
                      "tier": {
                        "type": "string",
                        "enum": [
                          "sketch",
                          "ink"
                        ]
                      },
                      "title": {
                        "type": "string"
                      },
                      "look": {
                        "type": "string",
                        "enum": [
                          "cinematic",
                          "gentle"
                        ],
                        "description": "Moving pictures: cinematic (more action, the default) or gentle (calmer). Same price."
                      }
                    }
                  },
                  {
                    "type": "object",
                    "required": [
                      "credits"
                    ],
                    "properties": {
                      "credits": {
                        "type": "integer",
                        "description": "The exact credits from /quote that the user agreed to."
                      },
                      "idempotency_key": {
                        "type": "string",
                        "description": "Optional. Same key again returns the first job, never a second charge."
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "402": {
            "description": "Not enough credits. Nothing started; packs has checkout links.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "409": {
            "description": "Price changed or no quoted price sent. Quote again and ask the user.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "description": "Plain-sentence error, safe to read out",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "Plain-sentence error, safe to read out",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/jobs/{job_id}": {
      "get": {
        "operationId": "job",
        "summary": "Progress of a make; when done, the link and video, images or text. Failed makes are refunded automatically.",
        "security": [
          {
            "museSignIn": [
              "credits:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "job_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Plain-sentence error, safe to read out",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Plain-sentence error, safe to read out",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/cap": {
      "post": {
        "operationId": "setDailyLimit",
        "summary": "Set the user's own daily credit limit for Muse (0 to 5000).",
        "security": [
          {
            "museSignIn": [
              "credits:read",
              "make"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "daily_credits"
                ],
                "properties": {
                  "daily_credits": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 5000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Plain-sentence error, safe to read out",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "museSignIn": {
        "type": "oauth2",
        "description": "The user signs in to their own Comic Timelines account. Only their own credits are spent.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://comictimelines.com/oauth/authorize",
            "tokenUrl": "https://comictimelines.com/oauth/token",
            "refreshUrl": "https://comictimelines.com/oauth/token",
            "scopes": {
              "credits:read": "See your credits",
              "make": "Make things with your credits"
            }
          }
        }
      }
    }
  }
}