{
  "openapi": "3.1.0",
  "info": {
    "title": "Beans Connect OAuth and webhook contract",
    "version": "1.0.0",
    "description": "Implemented synthetic contract. Hosted identity, deployment and real-vendor acceptance remain gates. Required PAR with invitation extension; mandatory S256 PKCE; none/client_secret_post; no OIDC. 16 KiB request bound. Refer to OAUTH-WEBHOOKS.md for strict field, lifetime, retry and permission semantics."
  },
  "servers": [
    {
      "url": "http://127.0.0.1:18789",
      "description": "Free local synthetic lab; configure a pinned HTTPS issuer for hosted acceptance"
    }
  ],
  "paths": {
    "/.well-known/oauth-authorization-server": {
      "get": {
        "operationId": "oauthMetadata",
        "security": [],
        "responses": {
          "200": {
            "description": "RFC 8414 metadata; issuer is pinned in deployment configuration"
          }
        }
      }
    },
    "/oauth/par": {
      "post": {
        "operationId": "pushAuthorization",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/PAR"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "properties": {
                    "request_uri": {
                      "type": "string"
                    },
                    "expires_in": {
                      "const": 90
                    }
                  },
                  "required": [
                    "request_uri",
                    "expires_in"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, grant, scope or expired PAR",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "401": {
            "description": "Client or owner session unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "403": {
            "description": "Current authority unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "409": {
            "description": "Configuration or consent changed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "429": {
            "description": "Admission limit; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "503": {
            "description": "Unavailable; credential exchange must not be blindly retried",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    },
    "/oauth/authorize": {
      "get": {
        "operationId": "launchOwnerConsent",
        "security": [],
        "parameters": [
          {
            "in": "query",
            "name": "client_id",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "in": "query",
            "name": "request_uri",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^urn:ietf:params:oauth:request_uri:[A-Za-z0-9_-]{43}$"
            }
          }
        ],
        "responses": {
          "303": {
            "description": "Fixed consent page with a short-lived fragment interaction; no pet content"
          },
          "400": {
            "description": "Invalid request, grant, scope or expired PAR",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "401": {
            "description": "Client or owner session unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "403": {
            "description": "Current authority unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "409": {
            "description": "Configuration or consent changed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "429": {
            "description": "Admission limit; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "503": {
            "description": "Unavailable; credential exchange must not be blindly retried",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    },
    "/oauth/interaction": {
      "post": {
        "operationId": "ownerConsent",
        "security": [
          {
            "ownerSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Interaction"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, grant, scope or expired PAR",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "401": {
            "description": "Client or owner session unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "403": {
            "description": "Current authority unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "409": {
            "description": "Configuration or consent changed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "429": {
            "description": "Admission limit; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "503": {
            "description": "Unavailable; credential exchange must not be blindly retried",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    },
    "/oauth/token": {
      "post": {
        "operationId": "exchangeOAuthToken",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/CodeGrant"
                  },
                  {
                    "$ref": "#/components/schemas/RefreshGrant"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Token"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, grant, scope or expired PAR",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "401": {
            "description": "Client or owner session unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "403": {
            "description": "Current authority unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "409": {
            "description": "Configuration or consent changed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "429": {
            "description": "Admission limit; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "503": {
            "description": "Unavailable; credential exchange must not be blindly retried",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    },
    "/oauth/revoke": {
      "post": {
        "operationId": "revokeOAuthFamily",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "client_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "client_secret": {
                    "type": "string",
                    "pattern": "^[A-Za-z0-9_-]{43}$"
                  },
                  "token": {
                    "type": "string",
                    "pattern": "^[A-Za-z0-9_-]{43}$"
                  },
                  "token_type_hint": {
                    "type": "string",
                    "enum": [
                      "access_token",
                      "refresh_token"
                    ]
                  }
                },
                "required": [
                  "client_id",
                  "token"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Invalid request, grant, scope or expired PAR",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "401": {
            "description": "Client or owner session unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "403": {
            "description": "Current authority unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "409": {
            "description": "Configuration or consent changed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "429": {
            "description": "Admission limit; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "503": {
            "description": "Unavailable; credential exchange must not be blindly retried",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    },
    "/partner/v1/connections/{connectionID}/webhooks": {
      "parameters": [
        {
          "in": "path",
          "name": "connectionID",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "post": {
        "operationId": "subscribeWebhook",
        "security": [
          {
            "partnerOAuth": [
              "events:read",
              "webhooks:subscribe"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "enabled"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Subscription"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, grant, scope or expired PAR",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "401": {
            "description": "Client or owner session unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "403": {
            "description": "Current authority unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "409": {
            "description": "Configuration or consent changed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "429": {
            "description": "Admission limit; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "503": {
            "description": "Unavailable; credential exchange must not be blindly retried",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "webhookSubscriptionStatus",
        "security": [
          {
            "partnerOAuth": [
              "webhooks:subscribe"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Subscription"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request, grant, scope or expired PAR",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "401": {
            "description": "Client or owner session unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "403": {
            "description": "Current authority unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "409": {
            "description": "Configuration or consent changed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "429": {
            "description": "Admission limit; Retry-After: 60",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "503": {
            "description": "Unavailable; credential exchange must not be blindly retried",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "changes": {
      "post": {
        "description": "Signed minimal event, at least once. Atomically deduplicate alongside the effect. Echo Beans-Verification for verification events. Verify raw bytes, timestamp, event ID and key ID.",
        "parameters": [
          {
            "in": "header",
            "name": "Beans-Signature",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "header",
            "name": "Beans-Event-ID",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "header",
            "name": "Beans-Key-ID",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/ChangeEvent"
                  },
                  {
                    "$ref": "#/components/schemas/VerificationEvent"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Accepted or authenticated duplicate; verification requires exact Beans-Verification header"
          },
          "503": {
            "description": "Transient failure; sender retries the same ID"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "OAuthError": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ]
      },
      "PAR": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "client_id": {
            "type": "string",
            "format": "uuid"
          },
          "client_secret": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{43}$"
          },
          "response_type": {
            "type": "string",
            "const": "code"
          },
          "redirect_uri": {
            "type": "string",
            "format": "uri"
          },
          "state": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{32,256}$"
          },
          "scope": {
            "type": "string"
          },
          "code_challenge": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{43}$"
          },
          "code_challenge_method": {
            "type": "string",
            "const": "S256"
          },
          "invitation": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{43}$"
          }
        },
        "required": [
          "client_id",
          "response_type",
          "redirect_uri",
          "state",
          "scope",
          "code_challenge",
          "code_challenge_method",
          "invitation"
        ]
      },
      "CodeGrant": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "client_id": {
            "type": "string",
            "format": "uuid"
          },
          "client_secret": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{43}$"
          },
          "grant_type": {
            "type": "string",
            "const": "authorization_code"
          },
          "code": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{43}$"
          },
          "redirect_uri": {
            "type": "string",
            "format": "uri"
          },
          "code_verifier": {
            "type": "string",
            "pattern": "^[A-Za-z0-9._~-]{43,128}$"
          }
        },
        "required": [
          "client_id",
          "grant_type",
          "code",
          "redirect_uri",
          "code_verifier"
        ]
      },
      "RefreshGrant": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "client_id": {
            "type": "string",
            "format": "uuid"
          },
          "client_secret": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{43}$"
          },
          "grant_type": {
            "type": "string",
            "const": "refresh_token"
          },
          "refresh_token": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{43}$"
          },
          "scope": {
            "type": "string"
          }
        },
        "required": [
          "client_id",
          "grant_type",
          "refresh_token"
        ]
      },
      "Token": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "access_token": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{43}$"
          },
          "refresh_token": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{43}$"
          },
          "token_type": {
            "type": "string",
            "enum": [
              "Bearer",
              "bearer"
            ]
          },
          "expires_in": {
            "type": "integer",
            "maximum": 600
          },
          "scope": {
            "type": "string"
          },
          "connection_id": {
            "type": "string",
            "format": "uuid"
          }
        },
        "required": [
          "access_token",
          "refresh_token",
          "token_type",
          "expires_in",
          "scope",
          "connection_id"
        ]
      },
      "Interaction": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "action": {
            "type": "string",
            "enum": [
              "preview",
              "approve",
              "deny"
            ]
          },
          "interaction": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{43}$"
          },
          "expectedRevision": {
            "type": "integer",
            "minimum": 1
          }
        },
        "required": [
          "action",
          "interaction"
        ]
      },
      "Subscription": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "subscribed": {
            "type": "boolean"
          },
          "resyncRequired": {
            "type": "boolean"
          },
          "fromRevision": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "subscribed"
        ]
      },
      "ChangeEvent": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "version": {
            "const": 1
          },
          "type": {
            "type": "string",
            "enum": [
              "record.changed",
              "record.deleted",
              "summary.delivered"
            ]
          },
          "connectionID": {
            "type": "string",
            "format": "uuid"
          },
          "revision": {
            "type": "string",
            "pattern": "^[0-9]+$"
          },
          "resourceID": {
            "type": "string",
            "format": "uuid"
          },
          "sourceRevision": {
            "type": [
              "integer",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "version",
          "type",
          "connectionID",
          "revision",
          "resourceID",
          "sourceRevision",
          "createdAt"
        ]
      },
      "VerificationEvent": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "version": {
            "const": 1
          },
          "type": {
            "type": "string",
            "const": "endpoint.verification"
          },
          "challenge": {
            "type": "string",
            "format": "uuid"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "version",
          "type",
          "challenge",
          "createdAt"
        ]
      }
    },
    "securitySchemes": {
      "ownerSession": {
        "type": "http",
        "scheme": "bearer",
        "description": "Pinned session-bound owner Clerk JWT; browser azp required, not a partner token"
      },
      "partnerOAuth": {
        "type": "oauth2",
        "description": "Required PAR first. Synthetic reviewed app and pet-specific current Human consent.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "/oauth/authorize",
            "tokenUrl": "/oauth/token",
            "scopes": {
              "records:read": "records:read",
              "records:write": "records:write",
              "summaries:request": "summaries:request",
              "summaries:read": "summaries:read",
              "events:read": "events:read",
              "webhooks:subscribe": "webhooks:subscribe"
            }
          }
        }
      }
    }
  }
}
