Zum Inhalt springen
Zomer Gregorio

Zomer Gregorio

Softwareentwickler

Lebenslauf
Frontend
  • TypeScript
  • React
  • Next.js
  • TanStack
  • Tailwind CSS
Backend
  • Node.js
  • Hono
  • Express
  • Django
Daten
  • PostgreSQL
  • Redis
  • Drizzle
  • Prisma
Infrastruktur / Tools
  • Docker
  • GitHub Actions
  • Cloudflare
  • Vercel

07 / Entwicklerressourcen

Integriere die öffentlichen REST-, OpenAPI- und MCP-Schnittstellen.

Die genaue technische Referenz ist unten auf Englisch verfügbar, damit Protokollbegriffe und Beispiele konsistent bleiben.

Zomer Gregorio Portfolio API

The Portfolio API gives agents and developers deterministic, structured access to Zomer Gregorio's published professional information without scraping HTML.

Endpoints

ServiceURL
REST API indexhttps://zomeru.dev/api/v1 (wird in einem neuen Tab geöffnet)
OpenAPI 3.2https://zomeru.dev/openapi.json (wird in einem neuen Tab geöffnet)
Portfolio MCPhttps://zomeru.dev/api/mcp (wird in einem neuen Tab geöffnet)
Documentation MCPhttps://zomeru.dev/api/mcp/docs (wird in einem neuen Tab geöffnet)
API cataloghttps://zomeru.dev/.well-known/api-catalog (wird in einem neuen Tab geöffnet)
Portfolio MCP server cardhttps://zomeru.dev/.well-known/mcp/server-card.json (wird in einem neuen Tab geöffnet)
Documentation MCP server cardhttps://zomeru.dev/.well-known/mcp/docs-server-card.json (wird in einem neuen Tab geöffnet)
Agent capabilitieshttps://zomeru.dev/.well-known/agent-skills/index.json (wird in einem neuen Tab geöffnet)

The REST contract is version 1.1.0. Breaking REST changes will use a new URL version such as /api/v2; non-breaking additions may remain in /api/v1.

REST resources

ResourceMethodEndpoint
API indexGET/api/v1
ProfileGET/api/v1/profile
ResumeGET/api/v1/resume
ExperienceGET/api/v1/experience
ProjectsGET/api/v1/projects
BlogsGET/api/v1/blogs?limit=10&offset=0
BlogGET/api/v1/blogs/{slug}
Tech stackGET/api/v1/tech-stack

All versioned portfolio REST resources support anonymous cross-origin reads and return cacheable JSON sourced from published Sanity content. The same-origin notification endpoints in OpenAPI are opt-in mutations and always return Cache-Control: no-store.

REST examples

curl https://zomeru.dev/api/v1
curl https://zomeru.dev/api/v1/profile
curl 'https://zomeru.dev/api/v1/blogs?limit=5&offset=0'
curl https://zomeru.dev/api/v1/resume

MCP

Connect a Streamable HTTP MCP client to:

https://zomeru.dev/api/mcp

Available portfolio tools:

  • get_profile — Get Zomer Gregorio's canonical professional profile and public links.
  • get_resume — Get Zomer Gregorio's structured resume, professional experience, technology stack, and current PDF URL.
  • get_tech_stack — Get the technology groups and tools currently published on Zomer's portfolio.
  • list_experience — List Zomer Gregorio's published professional experience in display order.
  • list_projects — List the projects currently published on Zomer Gregorio's portfolio.
  • list_blog_posts — List Zomer Gregorio's published technical articles with bounded pagination, canonical URLs, and optional case-insensitive title search.
  • get_blog_post — Get one published blog post by its canonical public slug.

The documentation MCP at https://zomeru.dev/api/mcp/docs (wird in einem neuen Tab geöffnet) exposes:

  • get_api_overview — Get API endpoints, protocol scope, quickstart, and versioning guidance.
  • get_authentication_guide — Get the anonymous-access policy and safe credential-handling guidance.
  • get_openapi_spec — Get the canonical OpenAPI 3.2 contract as JSON.

Example client configuration:

{
  "mcpServers": {
    "zomer-portfolio": {
      "url": "https://zomeru.dev/api/mcp"
    },
    "zomer-portfolio-docs": {
      "url": "https://zomeru.dev/api/mcp/docs"
    }
  }
}

The Inspector commands below target the portfolio MCP. Replace https://zomeru.dev/api/mcp with https://zomeru.dev/api/mcp/docs to inspect the documentation MCP.

Current MCP Inspector commands

npx @modelcontextprotocol/inspector --server-url https://zomeru.dev/api/mcp --transport http
npx @modelcontextprotocol/inspector --cli --server-url https://zomeru.dev/api/mcp --transport http --method tools/list

Authentication and safety

Authentication is none for published data and public email/push opt-in. Clients SHOULD NOT send credentials to those routes. The versioned portfolio operations are read-only; notification mutations accept only delivery data and never expose subscription lists. Webhook registration and notification summaries are private admin capabilities.

Caching and freshness

Responses use public HTTP caching and the existing five-minute Sanity revalidation window. Newly published content should appear reasonably quickly without creating a second cache architecture.

Outgoing blog webhooks

The portfolio can send a versioned HTTPS request after a blog is successfully published. Only the blog.published event is available. Webhook registration is admin-approved: send the endpoint URL, integration name, and destination type through the contact page (wird in einem neuen Tab geöffnet). The portfolio owner then registers it through the webhook controls on /admin or the protected POST /api/notifications/webhooks route. This avoids an unauthenticated arbitrary-URL relay.

Portfolio-owner registration request (the bearer value is the existing blog-generation admin capability, never a developer-supplied credential):

curl -X POST https://zomeru.dev/api/notifications/webhooks -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" --data '{"name":"Example integration","url":"https://example.com/hooks/zomer","destinationType":"generic","events":["blog.published"]}'

The same protected API lists non-sensitive summaries with GET /api/notifications/webhooks and disables one with DELETE /api/notifications/webhooks/{id}. The admin page can send a direct connectivity test with POST /api/notifications/webhooks/{id}/test; tests are not persisted as publication events and are not retried. Generic test requests use the signed webhook.test event type, while Slack and Discord receive a clearly labeled test message.

Generic destinations receive the following JSON contract:

{
  "id": "evt_...",
  "type": "blog.published",
  "apiVersion": "1",
  "createdAt": "2026-08-25T07:00:00.000Z",
  "data": {
    "blog": {
      "id": "sanity-document-id",
      "revision": "sanity-revision",
      "title": "Post title",
      "slug": "post-title",
      "excerpt": "Short description",
      "publishedAt": "2026-08-25T07:00:00.000Z",
      "url": "https://zomeru.dev/blogs/post-title"
    }
  }
}

The registration response returns the generic signing secret once. Store it in a secret manager. The destination URL and signing secret are encrypted at rest; neither is exposed by list or admin summary responses. Each generic request includes:

HeaderValue
X-Portfolio-Eventblog.published
X-Portfolio-DeliveryStable per-destination delivery UUID
X-Portfolio-TimestampUnix timestamp in seconds
X-Portfolio-Signaturev1= plus HMAC-SHA256 of timestamp.rawBody
X-Webhook-Version1

Verify the exact raw request body before parsing JSON and reject timestamps older than five minutes:

import { createHash, createHmac, timingSafeEqual } from "node:crypto";
 
function constantTimeEqual(actual: string, expected: string) {
  const digest = (value: string) => createHash("sha256").update(value).digest();
  return timingSafeEqual(digest(actual), digest(expected));
}
 
function signWebhookPayload(secret: string, timestamp: string, rawBody: string) {
  return createHmac("sha256", secret).update(timestamp + "." + rawBody).digest("hex");
}
 
export function verifyWebhookSignature(options: {
  rawBody: string;
  timestamp: string;
  signature: string;
  secret: string;
  toleranceSeconds?: number;
  now?: Date;
}) {
  const timestampSeconds = Number(options.timestamp);
  if (!Number.isSafeInteger(timestampSeconds)) return false;
  const nowSeconds = Math.floor((options.now ?? new Date()).getTime() / 1_000);
  if (Math.abs(nowSeconds - timestampSeconds) > (options.toleranceSeconds ?? 300)) return false;
  const expected =
    "v1=" + signWebhookPayload(options.secret, options.timestamp, options.rawBody);
  return constantTimeEqual(options.signature, expected);
}

Return any 2xx status after durably accepting a request. Timeouts, 408, 425, 429, and 5xx responses become eligible for retry after 1 minute, 5 minutes, 30 minutes, and 2 hours, with at most five attempts total. The portfolio owner starts queued retries from the protected admin page; the retry endpoint is not public or scheduled. Other 4xx responses fail permanently. Consumers must use X-Portfolio-Delivery or the event ID as an idempotency key because a network failure can cause a successful request to be delivered again.

Choose slack or discord during registration for platform-specific message payloads. Slack destinations are restricted to official incoming-webhook hosts. Discord destinations are restricted to official webhook URLs and use wait=true so a successful response confirms acceptance. These adapters do not receive the generic signature headers because their URL credentials are the platform authentication mechanism.

For local testing, expose an HTTPS handler with a tunnel such as Cloudflare Tunnel or ngrok, request admin approval for the temporary public URL, preserve the raw body in the handler, and publish only to an explicitly selected development Sanity dataset. Localhost and private/link-local IP targets are rejected by design.