I’m playing about making a simple web client using the bearer token for auth using APP TOKENS, and I’ve a question about the JSON API endpoint configurations.
My way of calling them is identical in a try/catch:
const res = await fetch(`${API_BASE}/posts/bookmarks`, {
headers: apiHeaders(),
});
const data = await res.json();
and
const res = await fetch(`${API_BASE}/posts/timeline`, {
headers: apiHeaders(),
});
const data = await res.json();
and
const res = await fetch(`${API_BASE}/posts/photos`, {
headers: apiHeaders(),
});
const data = await res.json();
and
const configRes = await fetch(`${API_BASE}/micropub?q=config`, {
headers: apiHeaders(),
});
const config = await configRes.json();
with the apiHeaders containing the bearer token - you get the idea.
Problem
Both the timeline, bookmark, photos and config endpoints work fine in an API test tool, sending the same bearer token, however when requesting using JS in a browser only the timeline and bookmark endpoints work, and the photos and config both seem to fail.
when I request https://micro.blog/posts/photos
I get back in the dev console:
Access to fetch at ‘https://micro.blog/posts/photos’ from origin ‘http://localhost:8000’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.
I’ve asked Claude and it reckons:
API tools like Postman send the GET request directly and see Access-Control-Allow-Origin: * on the response. But browsers don’t work that way.
Because our request includes an Authorization header (not a “simple” header), the browser first sends a preflight OPTIONS request before the actual GET. If the server doesn’t return proper CORS headers on that OPTIONS response, the browser blocks the request entirely — it never even sends the GET.
So the likely situation is:
- GET /posts/photos → returns Access-Control-Allow-Origin: * (what the API tool sees)
- OPTIONS /posts/photos → missing CORS headers (what the browser hits first, and fails on)
- OPTIONS /posts/timeline → returns CORS headers correctly (which is why that works)
API tools never send the preflight, so they don’t encounter the problem.Unfortunately there’s no client-side workaround — the Authorization header always triggers a preflight, and we can’t skip it.
This is a Micro.blog server configuration issue where the OPTIONS handler isn’t set up consistently across all endpoints.
Questions/ Suggestion
I guess my questions is:
a) is this correct and
b) why would some JSON endpoints be configured in a certain way and others not?
c) If so is it possible for the JSON API endpoints to be configured similarly so they work consistently? or at least setup so any PREFLIGHT checks work the same?