{
  "openapi": "3.0.3",
  "info": {
    "title": "Smart City Identity API",
    "version": "1.0.0",
    "description": "Central citizen enrollment and confidential-backend OIDC. Use authorization code + S256 PKCE. Never embed backend client secrets in React, Flutter or a distributed app. See /developers/ for onboarding and the separate program-side API contract."
  },
  "servers": [
    {
      "url": "https://identity.city.digikraft-technologies.com"
    }
  ],
  "paths": {
    "/api/v1/accounts/register/": {
      "post": {
        "operationId": "registerCitizen",
        "tags": [
          "Accounts"
        ],
        "summary": "Request a verification email",
        "description": "JSON only, maximum 8 KiB. No password or role fields. 10 requests per IP/hour and 3 per email/hour per endpoint. 202 is intentionally generic and does not guarantee delivery or disclose account existence.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Registration"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Eligible email queued; verify address and set password on the emailed page.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnrollmentAccepted"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON/fields or field validation errors.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/Detail"
                    },
                    {
                      "$ref": "#/components/schemas/ValidationErrors"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Wait before another email request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          },
          "503": {
            "description": "Email delivery unavailable; no local-registration fallback.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/accounts/recover/": {
      "post": {
        "operationId": "recoverAccount",
        "tags": [
          "Accounts"
        ],
        "summary": "Request a recovery email",
        "description": "JSON only, maximum 8 KiB. No password or role fields. 10 requests per IP/hour and 3 per email/hour per endpoint. 202 is intentionally generic and does not guarantee delivery or disclose account existence.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Recovery"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Eligible email queued; verify address and set password on the emailed page.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnrollmentAccepted"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON/fields or field validation errors.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/Detail"
                    },
                    {
                      "$ref": "#/components/schemas/ValidationErrors"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Wait before another email request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          },
          "503": {
            "description": "Email delivery unavailable; no local-registration fallback.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Detail"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/openid-configuration": {
      "get": {
        "operationId": "discoverIdentity",
        "tags": [
          "Discovery"
        ],
        "summary": "Discover live OIDC endpoints",
        "security": [],
        "responses": {
          "200": {
            "description": "Provider metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "issuer": {
                      "type": "string",
                      "format": "uri"
                    },
                    "authorization_endpoint": {
                      "type": "string"
                    },
                    "token_endpoint": {
                      "type": "string"
                    },
                    "userinfo_endpoint": {
                      "type": "string"
                    },
                    "jwks_uri": {
                      "type": "string"
                    },
                    "code_challenge_methods_supported": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  },
                  "required": [
                    "issuer",
                    "authorization_endpoint",
                    "token_endpoint",
                    "jwks_uri"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/o/.well-known/jwks.json": {
      "get": {
        "operationId": "getSigningKeys",
        "tags": [
          "Discovery"
        ],
        "summary": "Get public RSA signing keys",
        "security": [],
        "responses": {
          "200": {
            "description": "Public keys; never private material.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "keys": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "kty": {
                            "type": "string"
                          },
                          "kid": {
                            "type": "string"
                          },
                          "alg": {
                            "type": "string"
                          },
                          "use": {
                            "type": "string"
                          },
                          "n": {
                            "type": "string"
                          },
                          "e": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "kty",
                          "kid",
                          "n",
                          "e"
                        ]
                      }
                    }
                  },
                  "required": [
                    "keys"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/o/authorize/": {
      "get": {
        "operationId": "authorizeCitizen",
        "tags": [
          "OIDC"
        ],
        "summary": "Browser authorization redirect",
        "security": [],
        "description": "Top-level navigation, not a fetch API. Login/verified-email session required. Use a fresh state and nonce. Provider authorization code lifetime: 90 seconds.",
        "parameters": [
          {
            "name": "client_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "redirect_uri",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "response_type",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "code"
              ]
            }
          },
          {
            "name": "scope",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "example": "openid profile email offline_access"
            }
          },
          {
            "name": "state",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "nonce",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "code_challenge",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "code_challenge_method",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "S256"
              ]
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Login page or exact registered callback with code, state and iss; OAuth errors may redirect too.",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string"
                },
                "description": "Browser destination; never log authorization codes."
              }
            }
          },
          "200": {
            "description": "Interactive authorization page when needed."
          },
          "400": {
            "description": "Invalid OAuth request or redirect."
          },
          "403": {
            "description": "Email verification required."
          }
        }
      }
    },
    "/o/token/": {
      "post": {
        "operationId": "exchangeOrRefreshCentralToken",
        "tags": [
          "OIDC"
        ],
        "summary": "Exchange a code or rotate a refresh token",
        "security": [
          {
            "clientBasic": []
          }
        ],
        "description": "Backend only. Form encoding. Access lifetime: 900 seconds. Refresh tokens rotate; serialize requests and atomically replace stored tokens. Password/implicit grants unsupported. 120 token requests per IP/minute.",
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "oneOf": [
                  {
                    "type": "object",
                    "properties": {
                      "grant_type": {
                        "type": "string",
                        "enum": [
                          "authorization_code"
                        ]
                      },
                      "code": {
                        "type": "string"
                      },
                      "redirect_uri": {
                        "type": "string"
                      },
                      "code_verifier": {
                        "type": "string",
                        "minLength": 43,
                        "maxLength": 128
                      }
                    },
                    "required": [
                      "grant_type",
                      "code",
                      "redirect_uri",
                      "code_verifier"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "grant_type": {
                        "type": "string",
                        "enum": [
                          "refresh_token"
                        ]
                      },
                      "refresh_token": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "grant_type",
                      "refresh_token"
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Central tokens; keep encrypted on the program backend.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid grant, code/verifier, replay, expired token or unsupported grant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "401": {
            "description": "Invalid client authentication.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "429": {
            "description": "Too many token requests (text response)."
          }
        }
      }
    },
    "/o/userinfo/": {
      "get": {
        "operationId": "getCitizenClaims",
        "tags": [
          "OIDC"
        ],
        "summary": "Read scope-filtered citizen identity",
        "security": [
          {
            "centralBearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Identity claims for the central access token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CitizenClaims"
                }
              }
            }
          },
          "401": {
            "description": "Invalid, expired or revoked token."
          }
        }
      }
    },
    "/o/revoke/": {
      "post": {
        "operationId": "revokeCentralToken",
        "tags": [
          "OIDC"
        ],
        "summary": "Revoke an OAuth token",
        "security": [
          {
            "clientBasic": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "properties": {
                  "token": {
                    "type": "string"
                  },
                  "token_type_hint": {
                    "type": "string",
                    "enum": [
                      "access_token",
                      "refresh_token"
                    ]
                  }
                },
                "required": [
                  "token"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Revocation processed; unknown tokens also return success."
          },
          "401": {
            "description": "Invalid client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    },
    "/api/integrations/introspect/": {
      "post": {
        "operationId": "introspectCitizenSession",
        "tags": [
          "Sessions"
        ],
        "summary": "Check this client's bound session",
        "security": [
          {
            "clientBasic": []
          }
        ],
        "description": "Returns inactive for tokens from other clients, expired tokens, disabled connections or ended sessions. Maximum 1500 requests per IP/minute.",
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "properties": {
                  "token": {
                    "type": "string"
                  }
                },
                "required": [
                  "token"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Client-scoped active/inactive result.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Introspection"
                }
              }
            }
          },
          "401": {
            "description": "Invalid confidential client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          },
          "429": {
            "description": "Introspection rate limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthError"
                }
              }
            }
          }
        }
      }
    },
    "/api/health/": {
      "get": {
        "operationId": "getIdentityHealth",
        "tags": [
          "Health"
        ],
        "summary": "Check identity database connectivity",
        "security": [],
        "responses": {
          "200": {
            "description": "Database reachable.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "ok"
                      ]
                    },
                    "service": {
                      "type": "string",
                      "enum": [
                        "Smart City Identity"
                      ]
                    }
                  },
                  "required": [
                    "status",
                    "service"
                  ]
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Detail": {
        "type": "object",
        "properties": {
          "detail": {
            "type": "string"
          }
        },
        "required": [
          "detail"
        ]
      },
      "OAuthError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "error_description": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ]
      },
      "Registration": {
        "type": "object",
        "properties": {
          "full_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 150
          },
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 320
          },
          "phone_number": {
            "type": "string",
            "maxLength": 20
          }
        },
        "required": [
          "full_name",
          "email"
        ],
        "additionalProperties": false
      },
      "Recovery": {
        "type": "object",
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 320
          }
        },
        "required": [
          "email"
        ],
        "additionalProperties": false
      },
      "EnrollmentAccepted": {
        "type": "object",
        "properties": {
          "detail": {
            "type": "string"
          },
          "verification_required": {
            "type": "boolean",
            "enum": [
              true
            ]
          }
        },
        "required": [
          "detail",
          "verification_required"
        ]
      },
      "ValidationErrors": {
        "type": "object",
        "properties": {
          "errors": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "message": {
                    "type": "string"
                  },
                  "code": {
                    "type": "string"
                  }
                },
                "required": [
                  "message",
                  "code"
                ]
              }
            }
          }
        },
        "required": [
          "errors"
        ]
      },
      "TokenResponse": {
        "type": "object",
        "properties": {
          "access_token": {
            "type": "string"
          },
          "token_type": {
            "type": "string",
            "enum": [
              "Bearer"
            ]
          },
          "expires_in": {
            "type": "integer",
            "example": 900
          },
          "refresh_token": {
            "type": "string"
          },
          "id_token": {
            "type": "string"
          },
          "scope": {
            "type": "string"
          }
        },
        "required": [
          "access_token",
          "token_type",
          "expires_in"
        ]
      },
      "CitizenClaims": {
        "type": "object",
        "properties": {
          "sub": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 320
          },
          "email_verified": {
            "type": "boolean"
          },
          "sid": {
            "type": "string"
          },
          "security_version": {
            "type": "integer"
          }
        },
        "required": [
          "sub"
        ]
      },
      "Introspection": {
        "oneOf": [
          {
            "type": "object",
            "properties": {
              "active": {
                "type": "boolean",
                "enum": [
                  false
                ]
              }
            },
            "required": [
              "active"
            ]
          },
          {
            "type": "object",
            "properties": {
              "active": {
                "type": "boolean",
                "enum": [
                  true
                ]
              },
              "sub": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "email": {
                "type": "string",
                "format": "email",
                "maxLength": 320
              },
              "email_verified": {
                "type": "boolean",
                "enum": [
                  true
                ]
              },
              "phone_number": {
                "type": "string"
              },
              "phone_number_verified": {
                "type": "boolean",
                "enum": [
                  false
                ]
              },
              "client_id": {
                "type": "string"
              },
              "scope": {
                "type": "string"
              },
              "exp": {
                "type": "integer"
              },
              "sid": {
                "type": "string"
              },
              "security_version": {
                "type": "integer"
              }
            },
            "required": [
              "active",
              "sub",
              "name",
              "email",
              "email_verified",
              "phone_number",
              "phone_number_verified",
              "client_id",
              "scope",
              "exp",
              "sid",
              "security_version"
            ]
          }
        ]
      }
    },
    "securitySchemes": {
      "clientBasic": {
        "type": "http",
        "scheme": "basic",
        "description": "Issued confidential client ID and secret; backend only."
      },
      "centralBearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "Central OAuth access token, not a program JWT."
      }
    }
  }
}