Quick Start
All endpoints are served from the base URL below. Requests must be sent as JSON unless noted otherwise. Admin endpoints require a bearer token obtained from OAuth.
Base URL: https://api.codewithbotina.com
Auth Header: Authorization: Bearer {access_token}
CORS: Origin restricted tohttps://blog.codewithbotina.com in production.
Rate limits: Contact form and OAuth initiation are rate limited (HTTP 429 on abuse).
Authentication
OAuth Flow
Client → GET /api/auth/google
↘ Redirect to Google OAuth
Google → GET /api/auth/callback?code=...
Backend → Exchange code + set cookies
Backend → Redirect to /{lang}/auth/success (frontend)GEThttps://api.codewithbotina.com/api/auth/google
Initiate Google OAuth with PKCE and redirect the user.
Auth: Public
Query: next (optional full URL)
Example Request
GET /api/auth/google?next=https://blog.codewithbotina.com/es/Response
302 redirect to Google OAuth consent screen.
Errors
- 429 Too Many Requests
- 500 Internal Server Error
GEThttps://api.codewithbotina.com/api/auth/callback
OAuth callback handler. Exchanges the code for a session.
Auth: Public
Query: code (required), next (optional)
Example Request
GET /api/auth/callback?code=AUTH_CODE&next=https://blog.codewithbotina.com/es/Response
302 redirect to frontend auth success page.
Errors
- 400 Missing authorization code
- 500 Internal Server Error
GEThttps://api.codewithbotina.com/api/auth/me
Return the authenticated user profile.
Auth: Authenticated user
Headers
Authorization: Bearer {access_token}Success Response
{
"success": true,
"user": {
"id": "uuid",
"email": "diego@example.com",
"full_name": "Diego Alejandro Botina",
"avatar_url": "https://...",
"google_id": "123456",
"created_at": "2026-02-01T10:00:00Z",
"last_login": "2026-02-07T15:30:00Z",
"is_admin": false
}
}Errors
- 401 Unauthorized
- 500 Internal Server Error
POSThttps://api.codewithbotina.com/api/auth/refresh
Refresh an access token using a refresh token.
Auth: Public (refresh token required)
Request Body
{
"refresh_token": "v1.MR..."
}Success Response
{
"success": true,
"access_token": "eyJhbG...",
"refresh_token": "v1.MR...",
"expires_in": 3600
}Errors
- 400 Invalid JSON
- 401 Unauthorized
- 500 Internal Server Error
POSThttps://api.codewithbotina.com/api/auth/signout
Invalidate the current session and clear cookies.
Auth: Authenticated user
Headers
Authorization: Bearer {access_token}Success Response
{
"success": true,
"message": "Signed out successfully"
}Errors
- 401 Unauthorized
- 500 Internal Server Error
Posts
GEThttps://api.codewithbotina.com/api/posts
List posts with optional language filtering.
Auth: Public
Query: language, limit, offset
Example Request
GET /api/posts?language=es&limit=20&offset=0Success Response
{
"success": true,
"data": {
"posts": [
{
"id": "uuid",
"titulo": "Mi Post",
"slug": "mi-post",
"body": "...",
"imagen_url": null,
"fecha": "2026-03-01T12:00:00Z",
"updated_at": null,
"language": "es"
}
],
"limit": 20,
"offset": 0
}
}Errors
- 400 Unsupported language
- 500 Internal Server Error
GEThttps://api.codewithbotina.com/api/posts/{slug}
Fetch a single post and its tags.
Auth: Public
Query: language (optional)
Example Request
GET /api/posts/mi-post?language=esSuccess Response
{
"success": true,
"data": {
"id": "uuid",
"titulo": "Mi Post",
"slug": "mi-post",
"body": "...",
"imagen_url": null,
"fecha": "2026-03-01T12:00:00Z",
"updated_at": null,
"language": "es",
"tags": [
{ "id": "uuid", "name": "Tag", "slug": "tag", "usage_count": 3 }
]
}
}Errors
- 400 Unsupported language
- 404 Post not found
- 500 Internal Server Error
POSThttps://api.codewithbotina.com/api/posts/create
Create a new post (admin only).
Auth: Admin
Request Body
{
"titulo": "My Post Title",
"slug": "my-post-title",
"body": "Post content here...",
"language": "en",
"imagen_url": "https://...",
"tag_ids": ["uuid1", "uuid2"]
}Success Response
{
"success": true,
"message": "Post created successfully",
"data": {
"id": "uuid",
"titulo": "My Post Title",
"slug": "my-post-title",
"fecha": "2026-03-01T12:00:00Z"
}
}Errors
- 400 Validation failed
- 401 Unauthorized
- 403 Forbidden
- 500 Internal Server Error
PUThttps://api.codewithbotina.com/api/posts/{slug}/update
Update an existing post (admin only).
Auth: Admin
Request Body
{
"titulo": "Updated Title",
"slug": "updated-title",
"body": "Updated content",
"language": "en",
"imagen_url": null,
"tag_ids": ["uuid1", "uuid2"]
}Success Response
{
"success": true,
"message": "Post updated successfully",
"data": {
"id": "uuid",
"titulo": "Updated Title",
"slug": "updated-title"
}
}Errors
- 400 Validation failed
- 401 Unauthorized
- 403 Forbidden
- 404 Post not found
- 500 Internal Server Error
DELETEhttps://api.codewithbotina.com/api/posts/{slug}/delete
Delete a post (admin only). Requires confirmation.
Auth: Admin
Example Request
DELETE /api/posts/my-post/delete
DELETE /api/posts/my-post/delete?confirm=trueSuccess Response
{
"success": true,
"message": "Confirmation required",
"data": {
"requires_confirmation": true,
"comments_count": 12,
"reactions_count": 30
}
}Errors
- 401 Unauthorized
- 403 Forbidden
- 404 Post not found
- 500 Internal Server Error
GEThttps://api.codewithbotina.com/api/posts/{slug}/exists
Check if a slug already exists.
Auth: Public
Query: language (optional)
Success Response
{
"exists": true
}Errors
- 500 Internal Server Error
POSThttps://api.codewithbotina.com/api/posts/upload-image
Upload and optimize a featured image (admin only).
Auth: Admin
Headers
Content-Type: multipart/form-dataForm Fields
image: file
slug: string
title: stringSuccess Response
{
"success": true,
"message": "Image uploaded successfully",
"data": {
"url": "https://.../blog-images/my-post.webp",
"filename": "my-post.webp",
"size": 456789
}
}Errors
- 400 Invalid file
- 401 Unauthorized
- 403 Forbidden
- 500 Internal Server Error
GEThttps://api.codewithbotina.com/api/posts/{slug}/tags
Fetch tags attached to a post.
Auth: Public
Success Response
{
"success": true,
"data": {
"tags": [
{ "id": "uuid", "name": "Tag", "slug": "tag" }
]
}
}Errors
- 404 Post not found
- 500 Internal Server Error
GEThttps://api.codewithbotina.com/api/posts/test
Diagnostic endpoint for admin auth + Supabase connectivity.
Auth: Admin
Success Response
{
"success": true,
"message": "Admin access confirmed",
"data": {
"connected": true
}
}Errors
- 401 Unauthorized
- 403 Forbidden
- 500 Internal Server Error
Reactions
GEThttps://api.codewithbotina.com/api/reactions/{postId}
Get like/dislike counts for a post.
Auth: Public
Success Response
{
"success": true,
"data": { "post_id": "uuid", "likes": 10, "dislikes": 2, "total": 12 }
}Errors
- 404 Post not found
- 500 Internal Server Error
POSThttps://api.codewithbotina.com/api/reactions/{postId}/like
Toggle a like reaction (authenticated users).
Auth: Authenticated user
Success Response
{
"success": true,
"data": { "reaction": "like", "counts": { "likes": 11, "dislikes": 2, "total": 13 } }
}Errors
- 401 Unauthorized
- 404 Post not found
- 500 Internal Server Error
POSThttps://api.codewithbotina.com/api/reactions/{postId}/dislike
Toggle a dislike reaction (authenticated users).
Auth: Authenticated user
Success Response
{
"success": true,
"data": { "reaction": "dislike", "counts": { "likes": 10, "dislikes": 3, "total": 13 } }
}Errors
- 401 Unauthorized
- 404 Post not found
- 500 Internal Server Error
GEThttps://api.codewithbotina.com/api/reactions/user/{postId}
Get the current user reaction for a post.
Auth: Authenticated user
Success Response
{
"success": true,
"data": { "reaction": "like" }
}Errors
- 401 Unauthorized
- 404 Post not found
- 500 Internal Server Error
Contact
POSThttps://api.codewithbotina.com/api/contact
Submit a contact form message.
Auth: Public
Request Body
{
"nombre": "string (1-100 chars)",
"correo": "valid email",
"mensaje": "string (10-1000 chars)"
}Success Response
{
"success": true,
"message": "Contact form submitted successfully",
"data": {
"id": "uuid",
"nombre": "Diego Botina",
"correo": "test@example.com",
"mensaje": "...",
"created_at": "2023-10-27T10:00:00Z"
}
}Errors
- 400 Validation failed
- 429 Too Many Requests
- 500 Internal Server Error
⚡ Try It Out
Test the live API directly from your browser.
Health
GEThttps://api.codewithbotina.com/api/health
Service health check for monitoring.
Auth: Public
Success Response
{
"success": true,
"status": "ok",
"timestamp": "2026-03-01T12:00:00Z"
}Error Handling
All API errors follow a consistent JSON format:
{
"success": false,
"error": "Error message description",
"details": {
"field_name": "Specific validation error"
}
}Common Status Codes
- 400 Bad Request: Validation failed
- 401 Unauthorized: Missing or invalid token
- 403 Forbidden: Admin access required
- 404 Not Found: Resource missing
- 429 Too Many Requests: Rate limit exceeded
- 500 Internal Server Error: Unexpected server error
Comments
GET
https://api.codewithbotina.com/api/comments/{postId}List comments for a post.
Auth: Public
Success Response
Errors
POST
https://api.codewithbotina.com/api/comments/{postId}Create a comment for a post.
Auth: Authenticated user
Request Body
Success Response
Errors
PUT
https://api.codewithbotina.com/api/comments/{commentId}Update a comment (author only).
Auth: Authenticated user
Request Body
Success Response
Errors
DELETE
https://api.codewithbotina.com/api/comments/{commentId}Delete a comment (author or admin).
Auth: Authenticated user
Success Response
Errors
POST
https://api.codewithbotina.com/api/comments/{commentId}/pinPin a comment (admin only).
Auth: Admin
Success Response
Errors
POST
https://api.codewithbotina.com/api/comments/{commentId}/unpinUnpin a comment (admin only).
Auth: Admin
Success Response
Errors