{
  "openapi": "3.1.0",
  "info": {
    "title": "Odile Labs API",
    "version": "1.0.0",
    "summary": "AI answering phone lines for US businesses, billed per minute, and a meeting note-taker for Zoom, Google Meet and Microsoft Teams.",
    "description": "Your code acts for your own Odile Labs account with an API key. Make a key at https://odilelabs.com/developers after signing in with Google.\n\nTry first, free: POST /test-lines returns a shared test number and a 4-digit code that reaches an AI receptionist answering as your business (24 hours, 5 calls, nothing booked or charged).\n\nPhone lines: POST /lines sets up an After Hours answering line for a US business and returns a Stripe checkout link for its first minute pack (40 minutes for $10 or 200 for $50; $0.25 a minute, billed by the second with a 20-second minimum). Show the business owner https://odilelabs.com/terms first and send owner_confirmed_terms: true only after they agree. Nothing is built or charged until the pack is paid on Stripe's own page; the line's number is then emailed to the account. Calls and messages the line took are readable with GET /lines/{id}/calls and /lines/{id}/messages.\n\nMeeting notes: POST /meetings sends a note-taker to a call; it spends meeting minutes (the first 30 are free). The same tools are on the MCP server at https://odilelabs.com/mcp.",
    "contact": {
      "url": "https://odilelabs.com/contact"
    },
    "termsOfService": "https://odilelabs.com/terms"
  },
  "externalDocs": {
    "url": "https://odilelabs.com/developers"
  },
  "servers": [
    {
      "url": "https://api.odilelabs.com/v1"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "paths": {
    "/account": {
      "get": {
        "operationId": "getAccount",
        "summary": "The account this key belongs to, its phone lines, the minute packs and its meeting-minute balance",
        "responses": {
          "200": {
            "description": "The account",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/lines": {
      "get": {
        "operationId": "listLines",
        "summary": "The account's answering lines and set-ups waiting for payment",
        "responses": {
          "200": {
            "description": "Lines",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "post": {
        "operationId": "setUpLine",
        "summary": "Set up an answering line for a US business; returns the checkout link for its first minute pack",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LineSetup"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Waiting for payment",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LineSetupResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "503": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/lines/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LineId"
        }
      ],
      "get": {
        "operationId": "getLine",
        "summary": "One line: its status (setting up, queued or live), number, balance and forwarding codes",
        "responses": {
          "200": {
            "description": "The line",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/lines/{id}/calls": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LineId"
        },
        {
          "$ref": "#/components/parameters/Limit"
        },
        {
          "$ref": "#/components/parameters/Before"
        }
      ],
      "get": {
        "operationId": "listLineCalls",
        "summary": "Calls the line answered, newest first",
        "responses": {
          "200": {
            "description": "A page of calls",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time"
                          },
                          "caller": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "duration_seconds": {
                            "type": "integer"
                          },
                          "summary": {
                            "type": "string"
                          },
                          "urgent": {
                            "type": "boolean"
                          }
                        }
                      }
                    },
                    "next_before": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Pass as before= for the next page; null on the last page"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/lines/{id}/messages": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LineId"
        },
        {
          "$ref": "#/components/parameters/Limit"
        },
        {
          "$ref": "#/components/parameters/Before"
        }
      ],
      "get": {
        "operationId": "listLineMessages",
        "summary": "Messages the line took for the owner (caller name, number, what they need), newest first",
        "responses": {
          "200": {
            "description": "A page of messages",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time"
                          },
                          "name": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "phone": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "message": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "urgent": {
                            "type": "boolean"
                          }
                        }
                      }
                    },
                    "next_before": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/lines/{id}/minute-packs": {
      "parameters": [
        {
          "$ref": "#/components/parameters/LineId"
        }
      ],
      "post": {
        "operationId": "buyLineMinutes",
        "summary": "A Stripe checkout link for another minute pack on a per-minute line",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "pack"
                ],
                "properties": {
                  "pack": {
                    "$ref": "#/components/schemas/MinutePack"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Checkout link",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "checkout_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "pack": {
                      "type": "string"
                    },
                    "price_cents": {
                      "type": "integer"
                    },
                    "minutes": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/meetings": {
      "get": {
        "operationId": "listMeetings",
        "summary": "The account's meetings, newest first",
        "responses": {
          "200": {
            "description": "Meetings",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "scheduleMeetingNotes",
        "summary": "Send the note-taker to a Zoom, Google Meet or Microsoft Teams call; the recap is emailed afterwards",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "meeting_url"
                ],
                "properties": {
                  "meeting_url": {
                    "type": "string",
                    "description": "A Zoom, Google Meet or Microsoft Teams link"
                  },
                  "starts_at": {
                    "type": "string",
                    "description": "ISO 8601 time, or \"now\" (the default)"
                  },
                  "title": {
                    "type": "string",
                    "maxLength": 160
                  },
                  "max_minutes": {
                    "type": "integer",
                    "description": "Stop after this many minutes"
                  },
                  "recipients": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "email"
                    },
                    "description": "Who gets the recap; defaults to the account email and its saved recipients"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Scheduled"
          },
          "202": {
            "description": "Accepted, confirming"
          },
          "400": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/meetings/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/MeetingId"
        }
      ],
      "get": {
        "operationId": "getMeeting",
        "summary": "Status, what it cost, and who was sent the recap",
        "responses": {
          "200": {
            "description": "The meeting"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/meetings/{id}/transcript": {
      "parameters": [
        {
          "$ref": "#/components/parameters/MeetingId"
        }
      ],
      "get": {
        "operationId": "getMeetingTranscript",
        "summary": "The transcript, once there is one",
        "responses": {
          "200": {
            "description": "Transcript"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/meetings/{id}/recap": {
      "parameters": [
        {
          "$ref": "#/components/parameters/MeetingId"
        }
      ],
      "get": {
        "operationId": "getMeetingRecap",
        "summary": "The recap, once there is one",
        "responses": {
          "200": {
            "description": "Recap"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/meetings/{id}/cancel": {
      "parameters": [
        {
          "$ref": "#/components/parameters/MeetingId"
        }
      ],
      "post": {
        "operationId": "cancelMeeting",
        "summary": "Cancel a scheduled note-taker",
        "responses": {
          "200": {
            "description": "Cancelled"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/test-lines": {
      "post": {
        "operationId": "createTestLine",
        "summary": "Free: a shared test number and a 4-digit code that reaches an AI receptionist answering as your business",
        "description": "Call test_number, key test_code when asked, and talk to it like a customer. hear_url plays the same receptionist in a browser. A code works for 24 hours and 5 calls; 3 test lines a day per account. Nothing is booked, sent or charged.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "business_name",
                  "trade"
                ],
                "properties": {
                  "business_name": {
                    "type": "string",
                    "maxLength": 80
                  },
                  "trade": {
                    "type": "string",
                    "enum": [
                      "vet",
                      "garage",
                      "lock",
                      "hvac",
                      "plumbing",
                      "roofing",
                      "electrical",
                      "pest",
                      "appliance"
                    ]
                  },
                  "area": {
                    "type": "string",
                    "maxLength": 100,
                    "description": "Town or area, e.g. Wellington, FL"
                  },
                  "hours": {
                    "type": "string",
                    "maxLength": 400,
                    "description": "Opening hours in words"
                  },
                  "services": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 12
                  },
                  "timezone": {
                    "type": "string",
                    "description": "IANA timezone, default America/New_York"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The test line",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "sandbox": {
                      "type": "boolean"
                    },
                    "test_number": {
                      "type": "string"
                    },
                    "test_number_e164": {
                      "type": "string"
                    },
                    "test_code": {
                      "type": "string"
                    },
                    "hear_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "calls_allowed": {
                      "type": "integer"
                    },
                    "how": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          },
          "503": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Authorization: Bearer <your API key>. Keys are made and revoked at https://odilelabs.com/developers."
      }
    },
    "parameters": {
      "LineId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "MeetingId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 25
        }
      },
      "Before": {
        "name": "before",
        "in": "query",
        "description": "An ISO time; returns items older than it (use next_before from the previous page)",
        "schema": {
          "type": "string"
        }
      }
    },
    "schemas": {
      "MinutePack": {
        "type": "string",
        "enum": [
          "min40",
          "min200"
        ],
        "description": "min40 = 40 minutes for $10, min200 = 200 minutes for $50"
      },
      "Hours": {
        "type": "object",
        "properties": {
          "open": {
            "type": "string",
            "example": "9:00 AM"
          },
          "close": {
            "type": "string",
            "example": "5:00 PM"
          }
        }
      },
      "LineSetup": {
        "type": "object",
        "required": [
          "business_name",
          "business_phone",
          "owner_mobile",
          "owner_confirmed_terms"
        ],
        "properties": {
          "business_name": {
            "type": "string",
            "maxLength": 120
          },
          "business_phone": {
            "type": "string",
            "description": "The business's US phone number, with area code"
          },
          "owner_mobile": {
            "type": "string",
            "description": "The owner's US mobile number, with area code"
          },
          "owner_confirmed_terms": {
            "type": "boolean",
            "description": "true only after the owner has read https://odilelabs.com/terms and agreed"
          },
          "pack": {
            "$ref": "#/components/schemas/MinutePack"
          },
          "country": {
            "type": "string",
            "enum": [
              "US"
            ],
            "default": "US"
          },
          "city": {
            "type": "string",
            "maxLength": 80
          },
          "description": {
            "type": "string",
            "maxLength": 300,
            "description": "What the business does, in a sentence"
          },
          "services": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "maxItems": 12
          },
          "extra": {
            "type": "string",
            "maxLength": 600,
            "description": "Anything else the line should know"
          },
          "languages": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "es",
                "pt",
                "fr",
                "tr",
                "ru",
                "de"
              ]
            },
            "description": "Languages besides English the line answers in"
          },
          "hours": {
            "type": "object",
            "description": "Opening hours by day (Monday...Sunday); a day left out or \"closed\" is closed. Defaults to weekdays 9 to 5.",
            "additionalProperties": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/Hours"
                },
                {
                  "type": "string",
                  "enum": [
                    "closed"
                  ]
                }
              ]
            }
          }
        }
      },
      "LineSetupResult": {
        "type": "object",
        "properties": {
          "setup_id": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "business_name": {
            "type": "string"
          },
          "business_phone": {
            "type": "string"
          },
          "pack": {
            "type": "string"
          },
          "billing_mode": {
            "type": "string"
          },
          "minutes": {
            "type": "integer"
          },
          "price_cents": {
            "type": "integer"
          },
          "checkout_url": {
            "type": "string",
            "format": "uri",
            "description": "The owner pays here, on Stripe's own page"
          },
          "then": {
            "type": "string"
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      }
    },
    "responses": {
      "Error": {
        "description": "An error",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}
