{"openapi":"3.0.0","info":{"title":"Bahrain Address API","version":"2.0.0","description":"Registration and login are public. Address API reads require an API key generated from a registered account; writes require authenticated contributor access; edits, deletes, and administration require an admin account."},"tags":[{"name":"Authentication"},{"name":"API Keys"},{"name":"Governorates"},{"name":"Places"},{"name":"Blocks"},{"name":"Administration"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"},"apiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]},"User":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"email":{"type":"string","format":"email"},"role":{"type":"string","enum":["admin","contributor"]},"api_rate_limit_override":{"type":"integer","nullable":true},"created_at":{"type":"string","format":"date-time"}}},"Governorate":{"type":"object","properties":{"id":{"type":"integer"},"governorate":{"type":"string"},"place_count":{"type":"integer"},"block_count":{"type":"integer"}}},"Place":{"type":"object","properties":{"id":{"type":"integer"},"place":{"type":"string"},"governorate_id":{"type":"integer"},"governorate":{"type":"string"},"block_count":{"type":"integer"}}},"Block":{"type":"object","properties":{"id":{"type":"integer"},"block":{"type":"string","pattern":"^[0-9]{3,4}$"},"place_id":{"type":"integer"},"place":{"type":"string"},"governorate_id":{"type":"integer"},"governorate":{"type":"string"}}},"AuthResponse":{"type":"object","properties":{"token":{"type":"string"},"csrf_token":{"type":"string"},"user":{"$ref":"#/components/schemas/User"}}},"ApiKey":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"key_prefix":{"type":"string"},"expires_at":{"type":"string","format":"date-time","nullable":true},"last_used_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"}}}}},"paths":{"/api/openapi.json":{"get":{"tags":["Administration"],"summary":"Download this OpenAPI document","responses":{"200":{"description":"OpenAPI document"},"429":{"description":"Rate limit exceeded"}}}},"/api/auth/register":{"post":{"tags":["Authentication"],"summary":"Register a contributor account","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","email","password"],"properties":{"name":{"type":"string","minLength":2,"maxLength":100},"email":{"type":"string","format":"email"},"password":{"type":"string","minLength":8,"maxLength":128}}}}}},"responses":{"201":{"description":"Account created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthResponse"}}}},"400":{"description":"Invalid request"},"409":{"description":"Record conflict"},"429":{"description":"Rate limit exceeded"}}}},"/api/auth/login":{"post":{"tags":["Authentication"],"summary":"Log in","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","format":"email"},"password":{"type":"string"}}}}}},"responses":{"200":{"description":"Authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthResponse"}}}},"401":{"description":"Authentication required or invalid"},"429":{"description":"Rate limit exceeded"}}}},"/api/auth/me":{"get":{"tags":["Authentication"],"summary":"Get current user","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"responses":{"200":{"description":"Current user"},"401":{"description":"Authentication required or invalid"}}}},"/api/auth/logout":{"post":{"tags":["Authentication"],"summary":"Log out a cookie-authenticated dashboard session","security":[{"bearerAuth":[]}],"responses":{"204":{"description":"Logged out"},"401":{"description":"Authentication required or invalid"},"403":{"description":"Administrator access or valid CSRF token required"}}}},"/api/auth/api-keys":{"get":{"tags":["API Keys"],"summary":"List your API keys","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"API key list","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}}}}},"401":{"description":"Authentication required or invalid"},"403":{"description":"Administrator access or valid CSRF token required"}}},"post":{"tags":["API Keys"],"summary":"Create an API key; the secret is returned once","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":2,"maxLength":100}}}}}},"responses":{"201":{"description":"API key created"},"400":{"description":"Invalid request"},"401":{"description":"Authentication required or invalid"},"403":{"description":"Administrator access or valid CSRF token required"},"409":{"description":"Record conflict"},"429":{"description":"Rate limit exceeded"}}}},"/api/auth/api-keys/{id}":{"delete":{"tags":["API Keys"],"summary":"Revoke one of your API keys","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"204":{"description":"API key revoked"},"400":{"description":"Invalid request"},"401":{"description":"Authentication required or invalid"},"403":{"description":"Administrator access or valid CSRF token required"},"404":{"description":"API key not found"},"409":{"description":"Record conflict"},"429":{"description":"Rate limit exceeded"}}}},"/api/auth/users":{"get":{"tags":["Administration"],"summary":"List users (admin only)","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"responses":{"200":{"description":"User list","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/User"}}}}},"401":{"description":"Authentication required or invalid"},"403":{"description":"Administrator access or valid CSRF token required"}}}},"/api/auth/users/{id}/role":{"patch":{"tags":["Administration"],"summary":"Change a user role (admin only)","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["role"],"properties":{"role":{"type":"string","enum":["admin","contributor"]}}}}}},"responses":{"200":{"description":"Role updated"},"400":{"description":"Invalid request"},"401":{"description":"Authentication required or invalid"},"403":{"description":"Administrator access or valid CSRF token required"},"404":{"description":"User not found"},"409":{"description":"Record conflict"},"429":{"description":"Rate limit exceeded"}}}},"/api/auth/users/{id}/rate-limit":{"patch":{"tags":["Administration"],"summary":"Set or clear a user API rate-limit override (admin only)","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["api_rate_limit_override"],"properties":{"api_rate_limit_override":{"type":"integer","minimum":1,"maximum":100000,"nullable":true}}}}}},"responses":{"200":{"description":"Rate limit updated"},"400":{"description":"Invalid request"},"401":{"description":"Authentication required or invalid"},"403":{"description":"Administrator access or valid CSRF token required"},"404":{"description":"User not found"},"409":{"description":"Record conflict"},"429":{"description":"Rate limit exceeded"}}}},"/api/auth/activity":{"get":{"tags":["Administration"],"summary":"Read activity logs (admin only)","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}}],"responses":{"200":{"description":"Activity list"},"401":{"description":"Authentication required or invalid"},"403":{"description":"Administrator access or valid CSRF token required"}}}},"/api/auth/stats":{"get":{"tags":["Administration"],"summary":"Get bounded API usage and runtime stats (admin only)","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"days","in":"query","schema":{"type":"integer","enum":[1,7,30,90],"default":7}}],"responses":{"200":{"description":"Usage, route, user, API key, authentication, daily, and runtime statistics"},"400":{"description":"Invalid request"},"401":{"description":"Authentication required or invalid"},"403":{"description":"Administrator access or valid CSRF token required"},"429":{"description":"Rate limit exceeded"}}}},"/api/governorates":{"get":{"tags":["Governorates"],"summary":"List governorates","security":[{"apiKeyAuth":[]}],"responses":{"200":{"description":"Governorates list","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Governorate"}}}}},"401":{"description":"Authentication required or invalid"},"429":{"description":"Rate limit exceeded"}}},"post":{"tags":["Governorates"],"summary":"Create governorate","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["governorate"],"properties":{"governorate":{"type":"string","minLength":2,"maxLength":100}}}}}},"responses":{"201":{"description":"Governorates created"},"400":{"description":"Invalid request"},"401":{"description":"Authentication required or invalid"},"403":{"description":"Administrator access or valid CSRF token required"},"409":{"description":"Record conflict"},"429":{"description":"Rate limit exceeded"}}}},"/api/governorates/{id}":{"put":{"tags":["Governorates"],"summary":"Update governorate (admin only)","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["governorate"],"properties":{"governorate":{"type":"string","minLength":2,"maxLength":100}}}}}},"responses":{"200":{"description":"Governorates updated"},"400":{"description":"Invalid request"},"401":{"description":"Authentication required or invalid"},"403":{"description":"Administrator access or valid CSRF token required"},"404":{"description":"Governorates not found"},"409":{"description":"Record conflict"},"429":{"description":"Rate limit exceeded"}}},"delete":{"tags":["Governorates"],"summary":"Delete governorate (admin only)","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"204":{"description":"Governorates deleted"},"400":{"description":"Invalid request"},"401":{"description":"Authentication required or invalid"},"403":{"description":"Administrator access or valid CSRF token required"},"404":{"description":"Governorates not found"},"409":{"description":"Record conflict"},"429":{"description":"Rate limit exceeded"}}}},"/api/places":{"get":{"tags":["Places"],"summary":"List places","security":[{"apiKeyAuth":[]}],"responses":{"200":{"description":"Places list","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Place"}}}}},"401":{"description":"Authentication required or invalid"},"429":{"description":"Rate limit exceeded"}}},"post":{"tags":["Places"],"summary":"Create place","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["place","governorate_id"],"properties":{"place":{"type":"string","minLength":2,"maxLength":100},"governorate_id":{"type":"integer","minimum":1}}}}}},"responses":{"201":{"description":"Places created"},"400":{"description":"Invalid request"},"401":{"description":"Authentication required or invalid"},"403":{"description":"Administrator access or valid CSRF token required"},"409":{"description":"Record conflict"},"429":{"description":"Rate limit exceeded"}}}},"/api/places/{id}":{"put":{"tags":["Places"],"summary":"Update place (admin only)","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["place","governorate_id"],"properties":{"place":{"type":"string","minLength":2,"maxLength":100},"governorate_id":{"type":"integer","minimum":1}}}}}},"responses":{"200":{"description":"Places updated"},"400":{"description":"Invalid request"},"401":{"description":"Authentication required or invalid"},"403":{"description":"Administrator access or valid CSRF token required"},"404":{"description":"Places not found"},"409":{"description":"Record conflict"},"429":{"description":"Rate limit exceeded"}}},"delete":{"tags":["Places"],"summary":"Delete place (admin only)","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"204":{"description":"Places deleted"},"400":{"description":"Invalid request"},"401":{"description":"Authentication required or invalid"},"403":{"description":"Administrator access or valid CSRF token required"},"404":{"description":"Places not found"},"409":{"description":"Record conflict"},"429":{"description":"Rate limit exceeded"}}}},"/api/blocks":{"get":{"tags":["Blocks"],"summary":"List blocks","security":[{"apiKeyAuth":[]}],"responses":{"200":{"description":"Blocks list","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Block"}}}}},"401":{"description":"Authentication required or invalid"},"429":{"description":"Rate limit exceeded"}}},"post":{"tags":["Blocks"],"summary":"Create block","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["block","place_id"],"properties":{"block":{"type":"string","pattern":"^[0-9]{3,4}$"},"place_id":{"type":"integer","minimum":1}}}}}},"responses":{"201":{"description":"Blocks created"},"400":{"description":"Invalid request"},"401":{"description":"Authentication required or invalid"},"403":{"description":"Administrator access or valid CSRF token required"},"409":{"description":"Record conflict"},"429":{"description":"Rate limit exceeded"}}}},"/api/blocks/{id}":{"put":{"tags":["Blocks"],"summary":"Update block (admin only)","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["block","place_id"],"properties":{"block":{"type":"string","pattern":"^[0-9]{3,4}$"},"place_id":{"type":"integer","minimum":1}}}}}},"responses":{"200":{"description":"Blocks updated"},"400":{"description":"Invalid request"},"401":{"description":"Authentication required or invalid"},"403":{"description":"Administrator access or valid CSRF token required"},"404":{"description":"Blocks not found"},"409":{"description":"Record conflict"},"429":{"description":"Rate limit exceeded"}}},"delete":{"tags":["Blocks"],"summary":"Delete block (admin only)","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"204":{"description":"Blocks deleted"},"400":{"description":"Invalid request"},"401":{"description":"Authentication required or invalid"},"403":{"description":"Administrator access or valid CSRF token required"},"404":{"description":"Blocks not found"},"409":{"description":"Record conflict"},"429":{"description":"Rate limit exceeded"}}}},"/api/address-tree":{"get":{"tags":["Blocks"],"summary":"List flattened address records","security":[{"apiKeyAuth":[]}],"responses":{"200":{"description":"Address records"},"401":{"description":"Authentication required or invalid"},"429":{"description":"Rate limit exceeded"}}}},"/api/address/resolve":{"get":{"tags":["Places"],"summary":"Resolve a place name to address details","description":"Fuzzy-searches for places matching the given name and returns up to 20 results with their governorate details.","security":[{"apiKeyAuth":[]}],"parameters":[{"name":"place","in":"query","required":true,"description":"Place name to search for (partial match supported)","schema":{"type":"string","minLength":2,"maxLength":100}}],"responses":{"200":{"description":"Matching places","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"place_id":{"type":"integer"},"place":{"type":"string"},"governorate_id":{"type":"integer"},"governorate":{"type":"string"}}}}}}},"400":{"description":"Invalid request"},"401":{"description":"Authentication required or invalid"},"404":{"description":"No matching place found"},"429":{"description":"Rate limit exceeded"}}}},"/api/import":{"post":{"tags":["Administration"],"summary":"Import address CSV (admin only)","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["csv"],"properties":{"csv":{"type":"string","format":"binary"}}}}}},"responses":{"200":{"description":"Import result"},"400":{"description":"Invalid request"},"401":{"description":"Authentication required or invalid"},"403":{"description":"Administrator access or valid CSRF token required"},"409":{"description":"Record conflict"},"413":{"description":"File or row limit exceeded"},"429":{"description":"Rate limit exceeded"}}}}}}