CodeWithBotina API

v1.0 • Edge-deployed RESTful API

Checking status...

Backend services for the CodeWithBotina Blog. Built with Deno, Fresh, Supabase, and Resend.

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=0

Success 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=es

Success 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=true

Success 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-data

Form Fields

image: file
slug: string
title: string

Success 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

Tags

GEThttps://api.codewithbotina.com/api/tags

List all tags ordered by usage count.

Auth: Public

Success Response

{
  "success": true,
  "data": {
    "tags": [
      { "id": "uuid", "name": "Tag", "slug": "tag", "usage_count": 3 }
    ]
  }
}

Errors

  • 500 Internal Server Error

GEThttps://api.codewithbotina.com/api/tags/{slug}

Fetch tag details and posts for a tag.

Auth: Public

Success Response

{
  "success": true,
  "data": {
    "tag": { "id": "uuid", "name": "Tag", "slug": "tag", "usage_count": 3 },
    "posts": [
      { "id": "uuid", "titulo": "Post", "slug": "post", "fecha": "2026-03-01T12:00:00Z" }
    ]
  }
}

Errors

  • 404 Tag not found
  • 500 Internal Server Error

POSThttps://api.codewithbotina.com/api/tags/suggest

Generate tag suggestions based on title and body.

Auth: Public

Request Body

{
  "title": "Post title",
  "body": "Post body"
}

Success Response

{
  "success": true,
  "data": { "suggestions": [{ "id": "uuid", "name": "Tag", "slug": "tag" }] }
}

Errors

  • 400 Invalid request
  • 500 Internal Server Error

GEThttps://api.codewithbotina.com/api/tags/autocomplete

Autocomplete tag names.

Auth: Public

Query: q (min 2 chars)

Example Request

GET /api/tags/autocomplete?q=dev

Success Response

{
  "success": true,
  "data": {
    "tags": [
      { "id": "uuid", "name": "DevOps", "slug": "devops", "usage_count": 8 }
    ]
  }
}

Errors

  • 400 Query too short
  • 500 Internal Server Error

POSThttps://api.codewithbotina.com/api/tags/create

Create a new tag (admin only).

Auth: Admin

Request Body

{
  "name": "New Tag"
}

Errors

  • 401 Unauthorized
  • 403 Forbidden
  • 500 Internal Server Error

Comments

GEThttps://api.codewithbotina.com/api/comments/{postId}

List comments for a post.

Auth: Public

Success Response

{
  "success": true,
  "data": [
    { "id": "uuid", "content": "Great post", "user_id": "uuid", "is_pinned": false }
  ],
  "meta": { "total": 1, "pinned_count": 0 }
}

Errors

  • 404 Post not found
  • 500 Internal Server Error

POSThttps://api.codewithbotina.com/api/comments/{postId}

Create a comment for a post.

Auth: Authenticated user

Request Body

{
  "content": "Great post!"
}

Success Response

{
  "success": true,
  "data": { "id": "uuid", "content": "Great post!", "user_id": "uuid" }
}

Errors

  • 400 Validation failed
  • 401 Unauthorized
  • 500 Internal Server Error

PUThttps://api.codewithbotina.com/api/comments/{commentId}

Update a comment (author only).

Auth: Authenticated user

Request Body

{
  "content": "Updated comment"
}

Success Response

{
  "success": true,
  "data": { "id": "uuid", "content": "Updated comment" }
}

Errors

  • 401 Unauthorized
  • 403 Forbidden
  • 404 Comment not found
  • 500 Internal Server Error

DELETEhttps://api.codewithbotina.com/api/comments/{commentId}

Delete a comment (author or admin).

Auth: Authenticated user

Success Response

{
  "success": true,
  "message": "Comment deleted successfully"
}

Errors

  • 401 Unauthorized
  • 403 Forbidden
  • 500 Internal Server Error

POSThttps://api.codewithbotina.com/api/comments/{commentId}/pin

Pin a comment (admin only).

Auth: Admin

Success Response

{
  "success": true,
  "data": { "id": "uuid", "is_pinned": true }
}

Errors

  • 401 Unauthorized
  • 403 Forbidden
  • 404 Comment not found
  • 500 Internal Server Error

POSThttps://api.codewithbotina.com/api/comments/{commentId}/unpin

Unpin a comment (admin only).

Auth: Admin

Success Response

{
  "success": true,
  "data": { "id": "uuid", "is_pinned": false }
}

Errors

  • 401 Unauthorized
  • 403 Forbidden
  • 404 Comment not found
  • 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

Cookie Consent

POSThttps://api.codewithbotina.com/api/cookies/consent

Store GDPR/CCPA consent preferences.

Auth: Public (session_id required)

Request Body

{
  "session_id": "uuid",
  "functional_cookies": true,
  "analytics_cookies": false,
  "marketing_cookies": false
}

Errors

  • 400 Missing session_id
  • 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