{
  "openapi": "3.1.0",
  "info": {
    "title": "Dev Akintola API",
    "version": "1.0.0",
    "summary": "Read-only JSON for Tope Akintola's portfolio projects and blog posts.",
    "description": "A small read-only API over the content on devakintola.com, for agents and tools. It has no keys, no auth and no rate limits. Responses are cached at the edge for an hour. Every error is JSON with an `error` message and a `docs` link.\n\nEach single-item endpoint also answers in query style, which the site's WebMCP tools use: `/api/agent/projects?slug={slug}` returns the same body as `/api/agent/projects/{slug}`, and `/api/agent/posts?slug={slug}` the same as `/api/agent/posts/{slug}`.",
    "contact": {
      "name": "Tope Akintola",
      "url": "https://devakintola.com/contact"
    }
  },
  "externalDocs": {
    "description": "Human-readable docs",
    "url": "https://devakintola.com/docs/api"
  },
  "servers": [
    {
      "url": "https://devakintola.com"
    }
  ],
  "tags": [
    {
      "name": "projects",
      "description": "Portfolio projects and case studies."
    },
    {
      "name": "posts",
      "description": "Blog posts."
    }
  ],
  "paths": {
    "/api/agent/projects": {
      "get": {
        "operationId": "listProjects",
        "tags": [
          "projects"
        ],
        "summary": "List projects",
        "description": "Every portfolio project, newest first, with title, slug, summary and case-study URL. Use getProject for the full record. Passing `?slug={slug}` here returns one Project, the same as getProject.",
        "responses": {
          "200": {
            "description": "Projects, newest first. The example is trimmed to two items.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ProjectSummary"
                  }
                },
                "example": [
                  {
                    "title": "RPC Agent",
                    "slug": "rpc-agent",
                    "summary": "A CLI and MCP server that lets coding agents screenshot a URL at real phone, tablet and desktop viewports, framed in device bezels and signed in when the page needs it. The successor to my RPC Chrome extension, on npm as rpc-agent.",
                    "url": "https://devakintola.com/project/rpc-agent"
                  },
                  {
                    "title": "Hello",
                    "slug": "hello-greetings",
                    "summary": "A scroll essay on thirty-six greetings from thirty-six languages, built in Next.js with eighteen self-hosted scripts, a build that fails if any of them renders wrong, and a plain-text version of the whole thing.",
                    "url": "https://devakintola.com/project/hello-greetings"
                  }
                ]
              }
            }
          },
          "502": {
            "description": "The content store could not be reached. Retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Could not load projects",
                  "docs": "https://devakintola.com/docs/api"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/projects/{slug}": {
      "get": {
        "operationId": "getProject",
        "tags": [
          "projects"
        ],
        "summary": "Get a project",
        "description": "One portfolio project by slug: summary, role, stack, year, timeline, live link and case-study URL.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The project slug, as returned by listProjects.",
            "schema": {
              "type": "string"
            },
            "example": "rpc-agent"
          }
        ],
        "responses": {
          "200": {
            "description": "The project.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Project"
                },
                "example": {
                  "liveUrl": "https://github.com/akintola4/rpc-agent",
                  "publishedAt": "2026-10-30T12:00:00.000Z",
                  "serviceType": "Developer Tooling",
                  "slug": "rpc-agent",
                  "stack": "TypeScript, Node.js, Playwright, Model Context Protocol SDK, Zod, Clack, Vitest",
                  "summary": "A CLI and MCP server that lets coding agents screenshot a URL at real phone, tablet and desktop viewports, framed in device bezels and signed in when the page needs it. The successor to my RPC Chrome extension, on npm as rpc-agent.",
                  "timeline": "2 Days",
                  "title": "RPC Agent",
                  "yearOfBuild": 2026,
                  "url": "https://devakintola.com/project/rpc-agent"
                }
              }
            }
          },
          "404": {
            "description": "No project has this slug.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No project with slug \"no-such-project\"",
                  "docs": "https://devakintola.com/docs/api"
                }
              }
            }
          },
          "502": {
            "description": "The content store could not be reached. Retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Could not load project",
                  "docs": "https://devakintola.com/docs/api"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/posts": {
      "get": {
        "operationId": "listPosts",
        "tags": [
          "posts"
        ],
        "summary": "List blog posts",
        "description": "Every blog post, newest first, with title, slug, date, categories and URL. Filter with `category`. Use getPost for the description and author. Passing `?slug={slug}` here returns one Post, the same as getPost.",
        "parameters": [
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "Only return posts in this category. Matches a category title or slug, case-insensitive. An unknown category returns an empty array.",
            "schema": {
              "type": "string"
            },
            "example": "pwa"
          }
        ],
        "responses": {
          "200": {
            "description": "Posts, newest first. The unfiltered example is trimmed to two items.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/PostSummary"
                  }
                },
                "examples": {
                  "all": {
                    "summary": "GET /api/agent/posts",
                    "value": [
                      {
                        "title": "Building Motion Inspector: Turning Any Page's Animations Into Code",
                        "slug": "building-motion-inspector",
                        "date": "2026-11-02T08:00:00.000Z",
                        "categories": [
                          "frontend",
                          "UI/UX"
                        ],
                        "url": "https://devakintola.com/blog/building-motion-inspector"
                      },
                      {
                        "title": "Building Cookie Cutter: A Chrome Extension That Clicks Reject for You",
                        "slug": "building-cookie-cutter",
                        "date": "2026-11-02T00:00:00.000Z",
                        "categories": [
                          "frontend"
                        ],
                        "url": "https://devakintola.com/blog/building-cookie-cutter"
                      }
                    ]
                  },
                  "category": {
                    "summary": "GET /api/agent/posts?category=pwa",
                    "value": [
                      {
                        "title": "Building Progressive Web Apps (PWAs) in Next.js",
                        "slug": "building-progressive-web-apps-pwas-in-next-js",
                        "date": "2025-11-24T22:36:00.000Z",
                        "categories": [
                          "fullstack",
                          "mvp",
                          "pwa"
                        ],
                        "url": "https://devakintola.com/blog/building-progressive-web-apps-pwas-in-next-js"
                      }
                    ]
                  }
                }
              }
            }
          },
          "502": {
            "description": "The content store could not be reached. Retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Could not load posts",
                  "docs": "https://devakintola.com/docs/api"
                }
              }
            }
          }
        }
      }
    },
    "/api/agent/posts/{slug}": {
      "get": {
        "operationId": "getPost",
        "tags": [
          "posts"
        ],
        "summary": "Get a blog post",
        "description": "One blog post by slug: title, description, date, author, categories and URL.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The post slug, as returned by listPosts.",
            "schema": {
              "type": "string"
            },
            "example": "building-motion-inspector"
          }
        ],
        "responses": {
          "200": {
            "description": "The post.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Post"
                },
                "example": {
                  "title": "Building Motion Inspector: Turning Any Page's Animations Into Code",
                  "slug": "building-motion-inspector",
                  "description": "Why I built a Chrome side panel extension that pauses, scrubs and exports a page's animations as CSS, linear(), Motion or GSAP, how it catches 200 ms transitions, and where its exports still fall short.",
                  "date": "2026-11-02T08:00:00.000Z",
                  "author": "Tope Akintola",
                  "categories": [
                    "frontend",
                    "UI/UX"
                  ],
                  "url": "https://devakintola.com/blog/building-motion-inspector"
                }
              }
            }
          },
          "404": {
            "description": "No post has this slug.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No post with slug \"no-such-post\"",
                  "docs": "https://devakintola.com/docs/api"
                }
              }
            }
          },
          "502": {
            "description": "The content store could not be reached. Retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Could not load post",
                  "docs": "https://devakintola.com/docs/api"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ProjectSummary": {
        "type": "object",
        "description": "A project as it appears in listProjects.",
        "required": [
          "slug",
          "url"
        ],
        "properties": {
          "title": {
            "type": "string",
            "description": "Project name."
          },
          "slug": {
            "type": "string",
            "description": "Identifier for getProject."
          },
          "summary": {
            "type": "string",
            "description": "One or two sentences on what it is."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The case-study page."
          }
        }
      },
      "Project": {
        "type": "object",
        "description": "A full project record from getProject.",
        "required": [
          "slug",
          "url"
        ],
        "properties": {
          "title": {
            "type": "string",
            "description": "Project name."
          },
          "slug": {
            "type": "string",
            "description": "Identifier for getProject."
          },
          "summary": {
            "type": "string",
            "description": "One or two sentences on what it is."
          },
          "serviceType": {
            "type": "string",
            "description": "Tope's role or the kind of work."
          },
          "stack": {
            "type": "string",
            "description": "Technologies used, comma-separated."
          },
          "yearOfBuild": {
            "type": "integer",
            "description": "Year it was built."
          },
          "timeline": {
            "type": "string",
            "description": "How long it took, as written by Tope."
          },
          "liveUrl": {
            "type": "string",
            "format": "uri",
            "description": "Live site or source repository."
          },
          "publishedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the case study was published."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The case-study page."
          }
        }
      },
      "PostSummary": {
        "type": "object",
        "description": "A post as it appears in listPosts.",
        "required": [
          "slug",
          "categories",
          "url"
        ],
        "properties": {
          "title": {
            "type": "string",
            "description": "Post title."
          },
          "slug": {
            "type": "string",
            "description": "Identifier for getPost."
          },
          "date": {
            "type": "string",
            "format": "date-time",
            "description": "Publish date."
          },
          "categories": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Category titles."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The post page."
          }
        }
      },
      "Post": {
        "type": "object",
        "description": "A full post record from getPost.",
        "required": [
          "slug",
          "categories",
          "url"
        ],
        "properties": {
          "title": {
            "type": "string",
            "description": "Post title."
          },
          "slug": {
            "type": "string",
            "description": "Identifier for getPost."
          },
          "description": {
            "type": "string",
            "description": "Short summary of the post."
          },
          "date": {
            "type": "string",
            "format": "date-time",
            "description": "Publish date."
          },
          "author": {
            "type": "string",
            "description": "Author name."
          },
          "categories": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Category titles."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The post page."
          }
        }
      },
      "Error": {
        "type": "object",
        "description": "Every error response, including 404s for unknown /api paths.",
        "required": [
          "error",
          "docs"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "What went wrong."
          },
          "docs": {
            "type": "string",
            "format": "uri",
            "description": "Link to the API docs."
          }
        }
      }
    }
  }
}