{
  "openapi": "3.1.0",
  "info": {
    "title": "CutKit API",
    "version": "1.0.0",
    "summary": "Manage the scripts the CutKit iPhone teleprompter reads.",
    "description": "Every script in a person's CutKit library, over HTTPS with an API key. Anything you create here shows up in the iPhone app the next time it opens, and anything they write on the phone shows up here. The same operations are available as a remote MCP server at https://getcutkit.com/mcp and as the cutkit-scripts CLI. Human docs: https://getcutkit.com/docs/api",
    "contact": {
      "email": "hello@getcutkit.com",
      "url": "https://getcutkit.com/docs/api"
    }
  },
  "externalDocs": {
    "url": "https://getcutkit.com/docs/api",
    "description": "Guide, examples and the MCP setup"
  },
  "servers": [
    {
      "url": "https://getcutkit.com"
    }
  ],
  "security": [
    {
      "apiKey": []
    },
    {
      "oauth2": [
        "scripts"
      ]
    }
  ],
  "tags": [
    {
      "name": "Scripts",
      "description": "The teleprompter scripts in one library."
    },
    {
      "name": "Keys",
      "description": "The library an API key opens, and the key itself."
    }
  ],
  "paths": {
    "/api/v1/scripts": {
      "get": {
        "tags": [
          "Scripts"
        ],
        "operationId": "listScripts",
        "summary": "List scripts, or the changes since an instant",
        "description": "Without since: the live scripts, pinned first, then newest. With since: every script that changed at or after that instant, deleted ones included (deleted: true), oldest first. Pass the server_time of your previous answer as since to sync.",
        "parameters": [
          {
            "name": "since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "ISO 8601 instant. Use server_time from the previous answer."
          }
        ],
        "responses": {
          "200": {
            "description": "The scripts, and the server clock to pass as since next time.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "scripts",
                    "server_time"
                  ],
                  "properties": {
                    "scripts": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Script"
                      }
                    },
                    "server_time": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A field is the wrong type or too long.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key (missing_key, invalid_key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Scripts"
        ],
        "operationId": "createScript",
        "summary": "Create a script",
        "description": "With no title, the first line of the body becomes the title. Send an id to make a retry safe: a second POST with the same id answers 409 rather than adding a copy.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/ScriptInput"
                  },
                  {
                    "type": "object",
                    "required": [
                      "body"
                    ],
                    "properties": {
                      "id": {
                        "type": "string",
                        "pattern": "^[A-Za-z0-9_-]{8,64}$"
                      }
                    }
                  }
                ]
              },
              "example": {
                "title": "Launch video intro",
                "body": "Hi, I'm Sam. Here is what we shipped."
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "script"
                  ],
                  "properties": {
                    "script": {
                      "$ref": "#/components/schemas/Script"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A field is the wrong type or too long.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key (missing_key, invalid_key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "That id is taken (already_exists), or the library is full (library_full).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/scripts/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{8,64}$"
          },
          "description": "The script's id. The server makes a UUID when you don't choose one."
        }
      ],
      "get": {
        "tags": [
          "Scripts"
        ],
        "operationId": "getScript",
        "summary": "Read one script",
        "responses": {
          "200": {
            "description": "The script.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "script"
                  ],
                  "properties": {
                    "script": {
                      "$ref": "#/components/schemas/Script"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key (missing_key, invalid_key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No script with that id (not_found).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Scripts"
        ],
        "operationId": "putScript",
        "summary": "Create or replace a script with this id",
        "description": "Replaces the title, body and pin. A missing or deleted script is made (201). Omitting pinned sets it to false; omitting labels or archived keeps what the script had.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/ScriptInput"
                  },
                  {
                    "type": "object",
                    "required": [
                      "body"
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Replaced.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "script"
                  ],
                  "properties": {
                    "script": {
                      "$ref": "#/components/schemas/Script"
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "script"
                  ],
                  "properties": {
                    "script": {
                      "$ref": "#/components/schemas/Script"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A field is the wrong type or too long.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key (missing_key, invalid_key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The library is full (library_full).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Scripts"
        ],
        "operationId": "updateScript",
        "summary": "Change some fields of a script",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScriptInput"
              },
              "example": {
                "pinned": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "script"
                  ],
                  "properties": {
                    "script": {
                      "$ref": "#/components/schemas/Script"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A field is the wrong type or too long.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key (missing_key, invalid_key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No script with that id (not_found).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Scripts"
        ],
        "operationId": "deleteScript",
        "summary": "Delete a script",
        "description": "Leaves a tombstone, so a sync with since learns it went. Deleting twice is fine.",
        "responses": {
          "204": {
            "description": "Deleted."
          },
          "401": {
            "description": "Missing or invalid API key (missing_key, invalid_key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No script with that id (not_found).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/labels": {
      "get": {
        "tags": [
          "Labels"
        ],
        "operationId": "listLabels",
        "summary": "List the labels in use",
        "description": "Every label, how many live scripts carry it and its color, alphabetical.",
        "responses": {
          "200": {
            "description": "The labels.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "labels"
                  ],
                  "properties": {
                    "labels": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "label",
                          "scripts",
                          "color"
                        ],
                        "properties": {
                          "label": {
                            "type": "string"
                          },
                          "scripts": {
                            "type": "integer"
                          },
                          "color": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "enum": [
                              "red",
                              "orange",
                              "amber",
                              "green",
                              "teal",
                              "sky",
                              "blue",
                              "violet",
                              "pink",
                              null
                            ]
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key (missing_key, invalid_key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/labels/{name}": {
      "parameters": [
        {
          "name": "name",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "The label, matched without regard to case."
        }
      ],
      "patch": {
        "tags": [
          "Labels"
        ],
        "operationId": "renameLabel",
        "summary": "Rename a label on every script",
        "description": "Renaming onto a label that exists merges the two. Each script changed is stamped, so a phone syncing with since picks it up.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 24
                  }
                }
              },
              "example": {
                "name": "Client work"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "How many scripts changed."
          },
          "400": {
            "description": "A field is the wrong type or too long.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key (missing_key, invalid_key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No script has that label (not_found).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Labels"
        ],
        "operationId": "deleteLabel",
        "summary": "Take a label off every script",
        "description": "The scripts stay.",
        "responses": {
          "200": {
            "description": "How many scripts changed."
          },
          "401": {
            "description": "Missing or invalid API key (missing_key, invalid_key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No script has that label (not_found).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/labels/{name}/color": {
      "parameters": [
        {
          "name": "name",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "The label, matched without regard to case."
        }
      ],
      "put": {
        "tags": [
          "Labels"
        ],
        "operationId": "setLabelColor",
        "summary": "Color a label",
        "description": "One color per label for the whole library, the same on the phone and the web. Send null to clear it. The label needn't be on a script yet.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "color"
                ],
                "properties": {
                  "color": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "red",
                      "orange",
                      "amber",
                      "green",
                      "teal",
                      "sky",
                      "blue",
                      "violet",
                      "pink",
                      null
                    ]
                  }
                }
              },
              "example": {
                "color": "teal"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The label and its color."
          },
          "400": {
            "description": "A field is the wrong type or too long.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key (missing_key, invalid_key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/me": {
      "get": {
        "tags": [
          "Keys"
        ],
        "operationId": "getLibrary",
        "summary": "Check a key",
        "description": "Which library the key opens and how many live scripts it holds.",
        "responses": {
          "200": {
            "description": "The library.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "library",
                    "scripts"
                  ],
                  "properties": {
                    "library": {
                      "$ref": "#/components/schemas/Library"
                    },
                    "scripts": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key (missing_key, invalid_key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Keys"
        ],
        "operationId": "deleteLibrary",
        "summary": "Delete the library, its scripts and the key",
        "description": "The app calls this when somebody turns agent access off. Nothing is kept.",
        "responses": {
          "204": {
            "description": "Deleted."
          },
          "401": {
            "description": "Missing or invalid API key (missing_key, invalid_key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/keys": {
      "post": {
        "tags": [
          "Keys"
        ],
        "operationId": "createLibrary",
        "summary": "Make a new library and its key",
        "description": "Needs no key. The iPhone app calls this when somebody turns on agent access; you only need it to test against an empty library. The key is shown once. At most 5 an hour from one address.",
        "security": [],
        "responses": {
          "201": {
            "description": "The new key. Store it now, it isn't shown again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KeyIssued"
                }
              }
            }
          },
          "429": {
            "description": "Too many keys from this address (rate_limited).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/keys/rotate": {
      "post": {
        "tags": [
          "Keys"
        ],
        "operationId": "rotateKey",
        "summary": "Replace the key",
        "description": "Same scripts, new key. The old key stops working in the same moment.",
        "responses": {
          "200": {
            "description": "The new key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KeyIssued"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key (missing_key, invalid_key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "ck_ followed by 43 characters",
        "description": "Authorization: Bearer ck_... The person finds it in the CutKit app under Settings, Agent access."
      },
      "oauth2": {
        "type": "oauth2",
        "description": "Sign in and press Allow, the flow ChatGPT and Claude use for the MCP server. Public clients, PKCE S256 required. Discovery: /.well-known/oauth-authorization-server.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://getcutkit.com/app/authorize",
            "tokenUrl": "https://getcutkit.com/oauth/token",
            "refreshUrl": "https://getcutkit.com/oauth/token",
            "scopes": {
              "scripts": "Read and change the scripts in the person's library."
            }
          }
        }
      }
    },
    "schemas": {
      "Script": {
        "type": "object",
        "required": [
          "id",
          "title",
          "body",
          "pinned",
          "labels",
          "archived",
          "words",
          "created_at",
          "updated_at",
          "deleted"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string",
            "maxLength": 200
          },
          "body": {
            "type": "string",
            "maxLength": 200000,
            "description": "The words read out loud. Empty on a deleted script."
          },
          "pinned": {
            "type": "boolean"
          },
          "labels": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "string",
              "maxLength": 24
            },
            "description": "Groups shown in the library. Case-insensitively unique."
          },
          "archived": {
            "type": "boolean",
            "description": "Hidden from the library, not deleted."
          },
          "words": {
            "type": "integer",
            "description": "What the phone counts to estimate reading time."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "deleted": {
            "type": "boolean",
            "description": "Only ever true in an answer to ?since=."
          }
        }
      },
      "ScriptInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "title": {
            "type": "string",
            "maxLength": 200,
            "description": "One line. Empty or missing takes the body's first line."
          },
          "body": {
            "type": "string",
            "maxLength": 200000
          },
          "pinned": {
            "type": "boolean"
          },
          "labels": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "string",
              "maxLength": 24
            },
            "description": "Groups shown in the library. Case-insensitively unique."
          },
          "archived": {
            "type": "boolean",
            "description": "Hidden from the library, not deleted."
          }
        }
      },
      "Library": {
        "type": "object",
        "required": [
          "id",
          "key_hint",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "key_hint": {
            "type": "string",
            "description": "The key's first 7 and last 4 characters."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "rotated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "KeyIssued": {
        "type": "object",
        "required": [
          "key",
          "library"
        ],
        "properties": {
          "key": {
            "type": "string"
          },
          "library": {
            "$ref": "#/components/schemas/Library"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "missing_key",
                  "invalid_key",
                  "invalid_json",
                  "invalid_body",
                  "missing_body",
                  "invalid_title",
                  "title_too_long",
                  "body_too_long",
                  "invalid_pinned",
                  "invalid_labels",
                  "too_many_labels",
                  "invalid_archived",
                  "invalid_id",
                  "invalid_since",
                  "not_found",
                  "already_exists",
                  "library_full",
                  "rate_limited",
                  "not_configured",
                  "internal"
                ]
              },
              "message": {
                "type": "string"
              },
              "docs": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      }
    }
  }
}