API Reference¶
This document describes the IndieWeb endpoints provided by django-indieweb.
Note
The Micropub endpoint is now fully implemented with a pluggable content handler system. See the Micropub Implementation Guide documentation for implementation details.
Endpoints Overview¶
django-indieweb provides three main endpoints:
/indieweb/auth/- IndieAuth authorization endpoint/indieweb/token/- Token endpoint for exchanging auth codes/indieweb/micropub/- Micropub endpoint for creating content
IndieAuth Flow¶
sequenceDiagram
participant Client
participant User
participant AuthEndpoint as /indieweb/auth/
participant TokenEndpoint as /indieweb/token/
Client->>AuthEndpoint: GET with client_id, redirect_uri, state, me, code_challenge
AuthEndpoint->>User: Redirect to login if not authenticated
User->>AuthEndpoint: Login
AuthEndpoint->>Client: Redirect with auth code
Client->>TokenEndpoint: POST with code, client_id, redirect_uri, scope, code_verifier
TokenEndpoint->>Client: Return access token
Client->>Client: Store access token for future requests
Token Endpoint¶
URL: /indieweb/token/
Exchanges authorization codes for access tokens.
POST Request¶
Required Parameters:
code- The authorization code from the auth endpointclient_id- The client application’s URL
Optional Parameters:
redirect_uri- If sent, it must be a syntactically validhttp/httpsURL with no fragment delimiter (#) and no userinfo (user:pass@), and must match the value used in the original auth request after normalizing scheme and host case (path and query are compared verbatim); malformed values and mismatches are rejected withinvalid_grantme- The user’s profile URL; falls back to the value stored with the auth codescope- Optional scope confirmation. If omitted, the token is issued with the normalized scope stored with the auth code. If sent, the submitted value is normalized and must exactly match the stored auth-code scope; mismatches are rejected withinvalid_grantand no token is created or reissued. The token endpoint cannot broaden, narrow, or replace the scope approved during authorization. An explicitly emptyscope=parameter normalizes to no scope, so it only succeeds for an auth code that was issued with no scope.code_verifier- PKCE code verifier (RFC 7636), required when the auth code was issued with acode_challenge. 43-128 characters from the unreserved set[A-Za-z0-9._~-]. The server recomputes the challenge from the verifier (S256SHA-256 + base64url-without-padding, orplainstring equality) and compares in constant time. Mismatches, missing verifiers when a challenge was stored, and verifiers submitted when no challenge was stored are all rejected withinvalid_grant.
Example Request:
POST /indieweb/token/ HTTP/1.1
Host: yoursite.com
Content-Type: application/x-www-form-urlencoded
code=abc123&client_id=https://app.example.com&redirect_uri=https://app.example.com/callback&me=https://user.example.com&scope=create
Response:
Returns an access token.
Example Response:
HTTP/1.1 201 Created
Content-Type: application/x-www-form-urlencoded
access_token=xyz789&scope=create&me=https://user.example.com&expires_in=86400
The expires_in value is the remaining token lifetime in seconds. The
default lifetime is 24 hours and can be tuned with the
INDIEWEB_TOKEN_EXPIRES_IN setting (see Configuration). Reissuing a
token via the IndieAuth flow refreshes its expiration. Tokens whose
expires_at has passed are rejected with HTTP 401 by the Micropub endpoint.
Error Response:
HTTP/1.1 401 Unauthorized
Content-Type: text/plain
authentication error
Micropub Endpoint¶
URL: /indieweb/micropub/
The Micropub endpoint supports creating, updating, and deleting content through a pluggable handler system. See Micropub Implementation Guide for detailed implementation guide.
Authentication¶
All Micropub requests require a valid access token provided either:
In the
Authorizationheader:Authorization: Bearer <token>In the POST body:
Authorization=Bearer <token>
GET Request¶
Returns the authenticated user’s profile URL.
Example Request:
GET /indieweb/micropub/ HTTP/1.1
Host: yoursite.com
Authorization: Bearer xyz789
Response:
HTTP/1.1 200 OK
Content-Type: application/x-www-form-urlencoded
me=https://user.example.com
POST Request¶
Creates a new post using the configured content handler.
Supported Content Types:
application/x-www-form-urlencoded- Form-encoded dataapplication/json- JSON formatted data
Common Parameters:
h- The entry type (e.g., “entry”)content- The post contentname- The post title/namecategory- Categories (comma-separated in form data, array in JSON)in-reply-to- URL this post is replying tolocation- Geographic location in geo URI formatphoto- Photo URL(s)published- Publication date
Form-Encoded Example:
POST /indieweb/micropub/ HTTP/1.1
Host: yoursite.com
Authorization: Bearer xyz789
Content-Type: application/x-www-form-urlencoded
h=entry&content=Hello+World&category=test,indieweb
JSON Example:
POST /indieweb/micropub/ HTTP/1.1
Host: yoursite.com
Authorization: Bearer xyz789
Content-Type: application/json
{
"type": ["h-entry"],
"properties": {
"content": ["Hello World"],
"category": ["test", "indieweb"]
}
}
Response:
HTTP/1.1 201 Created
Location: https://yoursite.com/posts/123/
Update Action¶
Update an existing post via action=update. Per the Micropub specification
(§3.7), update requests must be JSON. The body carries any of the
replace, add, and delete keys; their semantics match the spec.
Request Body Keys:
action- Must be"update"url- The URL of the entry to update (required)replace(optional) - Object whose keys are property names and values are arrays of replacement values. The named properties are overwritten.add(optional) - Object whose keys are property names and values are arrays of values to append to those properties.delete(optional) - Either a list of property names to delete entirely, or an object whose keys are property names and values are arrays of specific values to remove from each property.
Example Request — replace:
POST /indieweb/micropub/ HTTP/1.1
Host: yoursite.com
Authorization: Bearer xyz789
Content-Type: application/json
{
"action": "update",
"url": "https://yoursite.com/posts/123/",
"replace": {"content": ["Updated content"]}
}
Example Request — add and delete combined:
POST /indieweb/micropub/ HTTP/1.1
Host: yoursite.com
Authorization: Bearer xyz789
Content-Type: application/json
{
"action": "update",
"url": "https://yoursite.com/posts/123/",
"add": {"category": ["new-tag"]},
"delete": ["draft"]
}
Response:
204 No Contentwhen the update succeeds and the entry’s URL is unchanged201 Createdwith aLocationheader when the configured handler returns an entry whose URL differs from the submitted URL (§3.7)400 Bad Requestbodyinvalid_requestwhen the entry is unknown to the handler,urlis missing, the body is not JSON or not a JSON object, the body contains none ofreplace/add/delete(§3.4 requires at least one), values insidereplace/addare not arrays (§3.4 requires arrays), ordeleteis neither a list of strings nor a map of property names to arrays500 Internal Server Errorwhen the configured handler raises an exception other thanValueError(logged vialogger.exception)
Form-encoded update requests are rejected with 400 invalid_request;
update bodies must be JSON.
Delete Action¶
Delete an existing post via action=delete. Both form-encoded and JSON
bodies are accepted; both require url.
Form-Encoded Example:
POST /indieweb/micropub/ HTTP/1.1
Host: yoursite.com
Authorization: Bearer xyz789
Content-Type: application/x-www-form-urlencoded
action=delete&url=https://yoursite.com/posts/123/
JSON Example:
POST /indieweb/micropub/ HTTP/1.1
Host: yoursite.com
Authorization: Bearer xyz789
Content-Type: application/json
{"action": "delete", "url": "https://yoursite.com/posts/123/"}
Response:
204 No Contenton success (delete cannot relocate)400 Bad Requestbodyinvalid_requestwhen the entry is unknown to the handler orurlis missing500 Internal Server Errorwhen the configured handler raises an exception other thanValueError
Undelete Action¶
Restore a previously-deleted post via action=undelete. Both form-encoded
and JSON bodies are accepted; both require url.
Form-Encoded Example:
POST /indieweb/micropub/ HTTP/1.1
Host: yoursite.com
Authorization: Bearer xyz789
Content-Type: application/x-www-form-urlencoded
action=undelete&url=https://yoursite.com/posts/123/
Response:
204 No Contentwhen the undelete succeeds and the entry’s URL is unchanged201 Createdwith aLocationheader when the configured handler returns an entry whose URL differs from the submitted URL (§3.10)400 Bad Requestbodyinvalid_requestwhen the URL is not in the handler’s deleted set orurlis missing500 Internal Server Errorwhen the configured handler raises an exception other thanValueError
Query Endpoints¶
The Micropub endpoint supports several query parameters:
Configuration Query:
GET /indieweb/micropub/?q=config HTTP/1.1
Authorization: Bearer xyz789
Returns supported post types and features.
Source Query:
GET /indieweb/micropub/?q=source&url=https://yoursite.com/posts/123/ HTTP/1.1
Authorization: Bearer xyz789
Accept: application/json
Returns the source content for an existing entry. The configured
MicropubContentHandler.get_entry(url, user) method receives the submitted
url unchanged and returns a MicropubEntry. A full source response
includes both the Microformats type and all entry properties:
{
"type": ["h-entry"],
"properties": {
"content": ["Hello World"],
"category": ["test", "indieweb"]
}
}
Clients can request a subset of properties using the array form
properties[]=NAME. When a filter is present, the response contains only
the requested properties that exist on the entry, and omits type to match
the Micropub source-query examples:
GET /indieweb/micropub/?q=source&url=https://yoursite.com/posts/123/&properties[]=content&properties[]=name HTTP/1.1
Authorization: Bearer xyz789
Accept: application/json
{
"properties": {
"content": ["Hello World"],
"name": ["Post title"]
}
}
GET ?q=source returns 400 invalid_request when url is missing or
unknown to the handler, and 500 Internal Server Error when the handler
raises an unexpected exception. Scope failures still return 403 with
body authorization error before source-query dispatch.
Syndication Targets Query:
GET /indieweb/micropub/?q=syndicate-to HTTP/1.1
Authorization: Bearer xyz789
Returns available syndication targets.
Error Responses¶
All endpoints may return these error responses:
400 Bad Request — ``invalid_grant``
Expired authorization code
Invalid authorization code
scopesent on token exchange does not normalize to the scope stored with the auth code, including attempts to add a scope to a no-scope auth code or attempts to submit an explicitly emptyscope=for a scoped auth coderedirect_urisent on token exchange is malformed (invalid URL, contains a#delimiter, includes userinfo, or uses a disallowed scheme)redirect_urisent on token exchange does not match the value stored with the auth code (after lowercasing scheme and host)code_verifieris missing on token exchange when the auth code was issued with acode_challengecode_verifierdoes not match the storedcode_challengeunder the storedcode_challenge_method(S256orplain)code_verifieris submitted on token exchange but nocode_challengewas stored with the auth codecode_verifieris malformed (length outside 43-128 or characters outside the unreserved set[A-Za-z0-9._~-])
400 Bad Request — ``invalid_request``
Missing required
codeorclient_idon token exchangeclient_idon token exchange is malformed (invalid URL, contains a#delimiter, includes userinfo, or uses a disallowed scheme)client_idon token exchange is rejected by the configuredINDIEWEB_CLIENT_ID_VALIDATORcallable, or that callable cannot be imported (fail-closed)Micropub
POSTwithContent-Type: application/jsonwhose body cannot be parsed as JSON or does not parse to a JSON object (rejected before scope/action dispatch so a malformed update body cannot fall through to the create path)Micropub
POST action=update/delete/undeleteagainst an unknownurl, missingurl, or — foraction=update— a non-JSON request body, an empty update payload (none ofreplace/add/delete), a non-array operation value (replace/addvalues must be arrays per §3.4), or adeletevalue that is neither a list of property names nor a map of property names to arrays. The Micropub specification’s response listings for these actions are limited; this project chose400 invalid_requestfor all three so client errors look consistent with the IndieAuth/token-endpoint behavior, rather than guessing handler-specific permission semantics with a404or403.Micropub
GET ?q=sourcewith a missingurlparameter or aurlunknown to the configured handler. Missing requestedproperties[]names are omitted from successful filtered responses instead of causing an error.
401 Unauthorized
Missing or invalid access token
Expired access token
User account associated with the token is inactive
403 Forbidden
Token lacks the scope required for the requested Micropub operation (
authorization error). Per-operation requirements:POSTentry create requirescreate(or the legacy aliaspost);POST action=updaterequiresupdate;POST action=deleterequiresdelete;POST action=undeleterequiresundelete;GET ?q=sourcerequiresupdate.GET ?q=config,GET ?q=syndicate-to, andGETwith noqonly require an authenticated token. Storedscopeis split on whitespace and matched as an exact token, socreateXYZdoes not satisfycreate.The stored token’s
client_idis rejected by the configuredINDIEWEB_CLIENT_ID_VALIDATORcallable, or that callable cannot be imported (invalid_client)
400 Bad Request — invalid redirect_uri
redirect_urion the authorization endpoint is malformed (invalid URL, contains a#delimiter, includes userinfo, or uses a disallowed scheme)
400 Bad Request — invalid client_id
client_idon the authorization endpoint is malformed (invalid URL, contains a#delimiter, includes userinfo, or uses a disallowed scheme)
400 Bad Request — ``invalid_client`` (authorization endpoint)
client_idon the authorization endpoint is rejected by the configuredINDIEWEB_CLIENT_ID_VALIDATORcallable, or that callable cannot be imported (fail-closed)
400 Bad Request — ``invalid_request`` (authorization endpoint)
code_challengeon the authorization endpoint is malformed (length outside 43-128 or characters outside the unreserved set[A-Za-z0-9._~-])code_challenge_methodis not one ofS256orplain
404 Not Found
Missing required parameters on the authorization endpoint
500 Internal Server Error
A configured Micropub handler raised an exception other than
ValueErrorwhile servicingPOST action=update/delete/undelete, or raised any exception while servicingGET ?q=source. The exception is logged vialogger.exceptionso the stack trace stays in the server log rather than the response body.
Scopes¶
The Micropub endpoint enforces scopes per operation. Stored scope values
are split on whitespace and compared as exact tokens, so createXYZ does
not satisfy create.
The authorization endpoint normalizes requested scope strings before showing
them on the consent screen and before storing them on Auth. Normalization
splits on whitespace, de-duplicates while preserving first-seen order, and
joins tokens with single spaces; empty or whitespace-only values become no
scope. Unknown scopes are preserved rather than rejected because
IndieAuth/Micropub scopes are extension-defined.
The token endpoint issues the stored auth-code scope. If a token exchange
includes scope, the submitted value is normalized and must match the stored
auth-code scope exactly. A mismatch returns 400 invalid_grant with content
type application/x-www-form-urlencoded and does not create or reissue a
token. An explicitly empty scope= parameter normalizes to no scope and is
accepted only when the auth code was issued with no scope.
create- Required forPOSTrequests that create new posts. The legacy aliaspostis also accepted.update- Required forPOST action=updateand forGET ?q=source(which is typically used to fetch a post for editing).delete- Required forPOST action=delete.undelete- Required forPOST action=undelete.post- Legacy alias forcreate.
GET ?q=config, GET ?q=syndicate-to, and GET with no q only
require an authenticated token; no specific scope is enforced.
Multiple scopes can be requested by separating with spaces: scope=create update
Rate Limiting¶
Currently, no rate limiting is implemented.
CORS Support¶
CORS headers are not automatically added. Configure your Django middleware if needed.
H-Card Support¶
While h-cards are not accessed via HTTP endpoints, django-indieweb provides models and template tags for managing user profile data.
Models¶
Profile Model
Store h-card data for users:
from indieweb.models import Profile
profile = Profile.objects.create(
user=user,
name="Display Name",
h_card={
"name": ["Display Name"],
"url": ["https://example.com"],
"photo": ["https://example.com/photo.jpg"]
}
)
Utilities¶
Parsing Functions
from indieweb.h_card import parse_h_card, validate_h_card
# Parse h-card from HTML
h_card_data = parse_h_card(html_string)
# Validate h-card structure
is_valid = validate_h_card(h_card_data)
See H-Cards for detailed documentation.