The Infraread API provides programmatic access to your RSS feed reader. All API endpoints are versioned and require authentication via Laravel Sanctum personal access tokens.
Base URL: /api/v1
Authentication: Bearer token (Sanctum personal access tokens)
Content Type: application/json
All API endpoints require authentication using a personal access token.
- Log into the web interface
- Go to Settings → API Tokens
- Create a new token
- Use the token in the
Authorizationheader:Bearer YOUR_TOKEN_HERE
curl -H "Authorization: Bearer YOUR_TOKEN_HERE" \
-H "Content-Type: application/json" \
https://your-domain.com/api/v1/postsRetrieve a paginated list of posts with optional filtering.
Endpoint: GET /api/v1/posts
Query Parameters:
source_id(integer, optional) - Filter by sourcecategory_id(integer, optional) - Filter by categoryinclude(string, optional) - Include relationships:source,category,source,categorypage(integer, optional) - Page number for pagination
Example Request:
GET /api/v1/posts?include=source,category&source_id=1Example Response:
{
"data": [
{
"id": 1,
"title": "Sample Post Title",
"content": "Post content here...",
"url": "https://example.com/post",
"read": false,
"posted_at": "2025-08-14T10:00:00.000000Z",
"source": {
"id": 1,
"name": "Tech Blog",
"description": "Latest tech news"
},
"category": {
"id": 1,
"description": "Technology"
}
}
],
"links": {
"first": "http://localhost/api/v1/posts?page=1",
"last": "http://localhost/api/v1/posts?page=5",
"prev": null,
"next": "http://localhost/api/v1/posts?page=2"
},
"meta": {
"current_page": 1,
"per_page": 15,
"total": 67
}
}Endpoint: GET /api/v1/posts/{id}
Endpoint: PATCH /api/v1/posts/{id}/read-status
Request Body:
{
"read": true
}Example Response:
{
"message": "Post marked as read",
"data": {
"id": 1,
"read": true
}
}Mark multiple posts as read or unread (up to 1000 posts per request).
Endpoint: PATCH /api/v1/posts/bulk-read-status
Request Body:
{
"post_ids": [1, 2, 3, 4, 5],
"read": true
}Example Response:
{
"message": "5 posts marked as read",
"data": {
"updated_count": 5,
"requested_count": 5
}
}Efficiently mark all posts as read/unread with optional filtering.
Endpoint: PATCH /api/v1/posts/mark-all-read
Request Body:
{
"read": true,
"source_id": 1,
"category_id": 2,
"before_date": "2025-08-14"
}Example Response:
{
"message": "25 posts marked as read",
"data": {
"updated_count": 25
}
}Generate an AI summary for a post (rate limited).
Endpoint: POST /api/v1/posts/{id}/summary
Example Response:
{
"data": {
"summary": "This article discusses the latest developments in AI technology..."
}
}Endpoint: GET /api/v1/sources
Query Parameters:
include(string, optional) - Include relationships:category
Endpoint: GET /api/v1/sources/{id}
Add a new RSS feed source with automatic discovery.
Endpoint: POST /api/v1/sources
Request Body:
{
"url": "https://techcrunch.com/feed/",
"category_id": 1,
"name": "TechCrunch",
"description": "Technology news and analysis"
}Example Response:
{
"message": "Source created successfully",
"data": {
"id": 5,
"name": "TechCrunch",
"description": "Technology news and analysis",
"url": "https://techcrunch.com",
"fetcher_source": "https://techcrunch.com/feed/",
"category_id": 1,
"active": true,
"metadata": {
"discovered_feeds": ["https://techcrunch.com/feed/"],
"site_title": "TechCrunch",
"site_description": "Latest technology news"
}
}
}Endpoint: PUT /api/v1/sources/{id}
Endpoint: DELETE /api/v1/sources/{id}
Force refresh posts from a source.
Endpoint: POST /api/v1/sources/{id}/refresh
Example Response:
{
"message": "Source refresh initiated",
"data": {
"source_id": 1,
"status": "queued"
}
}Endpoint: GET /api/v1/categories
Example Response:
{
"data": [
{
"id": 1,
"description": "Technology",
"sources_count": 5,
"created_at": "2025-08-14T10:00:00.000000Z",
"updated_at": "2025-08-14T10:00:00.000000Z"
}
]
}Endpoint: GET /api/v1/categories/{id}
Endpoint: POST /api/v1/categories
Request Body:
{
"description": "Science News"
}Endpoint: PUT /api/v1/categories/{id}
Deletes a category and moves all sources to "Uncategorized".
Endpoint: DELETE /api/v1/categories/{id}
Example Response:
{
"message": "Category deleted successfully",
"data": {
"sources_moved": 3,
"moved_to_category": "Uncategorized"
}
}Export all sources as OPML format for backup or migration.
Endpoint: GET /api/v1/export-opml
Example Response:
{
"message": "OPML exported successfully",
"data": {
"content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<opml version=\"2.0\">...",
"filename": "infraread-feeds-2025-08-14.opml",
"sources_count": 25,
"categories_count": 5
}
}Preview what will be imported from an OPML file without executing the import.
Endpoint: POST /api/v1/preview-opml
Request Body: Multipart form data
opml(file) - OPML file to preview
Example Response:
{
"message": "OPML preview generated successfully",
"data": {
"categories": [
{
"name": "Technology",
"sources": [
{
"name": "TechCrunch",
"url": "https://techcrunch.com/feed/",
"site_url": "https://techcrunch.com"
}
],
"source_count": 1
}
],
"uncategorized_sources": [],
"total_categories": 1,
"total_sources": 1
}
}Import sources from an OPML file.
Endpoint: POST /api/v1/import-opml
Request Body: Multipart form data
opml(file) - OPML file to importmode(string, optional) - Import mode:replace(default) ormerge
Example Response:
{
"message": "OPML imported successfully",
"data": {
"mode": "replace",
"categories_created": 3,
"sources_created": 15,
"sources_skipped": 0,
"errors": []
}
}All endpoints return consistent error responses:
{
"message": "The given data was invalid.",
"errors": {
"url": ["The url field is required."],
"category_id": ["The category id must be an integer."]
}
}{
"message": "Unauthenticated."
}{
"message": "No query results for model [App\\Models\\Post] 999"
}{
"message": "Too Many Attempts."
}{
"message": "Server Error",
"error": "An unexpected error occurred"
}- Summary Generation: 10 requests per minute per user
- Source Management: 60 requests per minute per user
- General API: 100 requests per minute per user
All list endpoints use Laravel's standard pagination:
{
"data": [...],
"links": {
"first": "http://localhost/api/v1/posts?page=1",
"last": "http://localhost/api/v1/posts?page=5",
"prev": null,
"next": "http://localhost/api/v1/posts?page=2"
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 5,
"per_page": 15,
"to": 15,
"total": 67
}
}# 1. Get your API token from the web interface first
# 2. List all posts
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://your-domain.com/api/v1/posts
# 3. Mark specific posts as read
curl -X PATCH \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"post_ids": [1,2,3], "read": true}' \
https://your-domain.com/api/v1/posts/bulk-read-status
# 4. Add a new source
curl -X POST \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/feed.xml", "category_id": 1}' \
https://your-domain.com/api/v1/sources
# 5. Export OPML backup
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://your-domain.com/api/v1/export-opmlThe Jobs API allows you to trigger background processing tasks and monitor their status. All jobs are processed asynchronously using Laravel's queue system.
Triggers a background job to manually refresh posts from a specific RSS source.
Endpoint: POST /api/v1/jobs/sources/{source}/refresh
Parameters:
source(integer, required) - The source ID to refresh
Example Request:
POST /api/v1/jobs/sources/1/refreshExample Response:
{
"message": "Source refresh job dispatched successfully",
"data": {
"source_id": 1,
"source_name": "Tech News",
"job_dispatched": true,
"estimated_completion": "2025-08-16T10:30:00Z"
}
}Dispatches a job to generate an AI summary for a specific post.
Endpoint: POST /api/v1/jobs/posts/{post}/summary
Parameters:
post(integer, required) - The post ID to summarizesentences(integer, optional) - Number of sentences for summary (default: 3, max: 10)
Example Request:
POST /api/v1/jobs/posts/123/summary
Content-Type: application/json
{
"sentences": 5
}Example Response:
{
"message": "Summary generation job dispatched successfully",
"data": {
"post_id": 123,
"cache_key": "summary_post_123_5",
"job_dispatched": true,
"estimated_completion": "2025-08-16T10:32:00Z",
"status_check_url": "/api/v1/jobs/summary-status/summary_post_123_5"
}
}Check the status of a summary generation job.
Endpoint: GET /api/v1/jobs/summary-status/{cacheKey}
Parameters:
cacheKey(string, required) - The cache key returned from generate summary job
Example Request:
GET /api/v1/jobs/summary-status/summary_post_123_5Example Response (Processing):
{
"message": "Summary generation in progress",
"data": {
"status": "processing",
"cache_key": "summary_post_123_5",
"started_at": "2025-08-16T10:30:00Z"
}
}Example Response (Completed):
{
"message": "Summary generation completed",
"data": {
"status": "completed",
"cache_key": "summary_post_123_5",
"summary": "This is the generated AI summary of the post content...",
"sentences": 5,
"completed_at": "2025-08-16T10:31:30Z"
}
}Get information about the job queue system status.
Endpoint: GET /api/v1/jobs/queue-status
Example Request:
GET /api/v1/jobs/queue-statusExample Response:
{
"message": "Queue status retrieved successfully",
"data": {
"queues": {
"default": {
"pending_jobs": 5,
"failed_jobs": 0
},
"refresh": {
"pending_jobs": 2,
"failed_jobs": 1
}
},
"total_pending": 7,
"total_failed": 1,
"system_healthy": true,
"generated_at": "2025-08-16T10:30:00Z"
}
}The Metrics API provides observability and monitoring capabilities for your RSS reader system. These endpoints help track performance, health, and system statistics.
Get detailed metrics for a specific RSS source.
Endpoint: GET /api/v1/metrics/sources/{source}
Parameters:
source(integer, required) - The source ID to get metrics for
Example Request:
GET /api/v1/metrics/sources/1Example Response:
{
"message": "Source metrics retrieved successfully",
"data": {
"source_id": 1,
"source_name": "Tech News",
"source_url": "https://technews.com/feed.xml",
"metrics": {
"last_fetched_at": "2025-08-16T09:15:00Z",
"last_fetch_duration_ms": 1200,
"consecutive_failures": 0,
"status": "active",
"status_description": "Working normally",
"last_error_at": null,
"last_error_message": null,
"next_attempt_at": null,
"should_skip_backoff": false,
"posts_count": 150,
"unread_posts_count": 12,
"latest_post_date": "2025-08-16T08:30:00Z",
"is_healthy": true,
"is_failed": false
}
}
}Get comprehensive system-wide statistics and performance metrics.
Endpoint: GET /api/v1/metrics/system
Example Request:
GET /api/v1/metrics/systemExample Response:
{
"message": "System processing statistics retrieved successfully",
"data": {
"sources": {
"total_sources": 15,
"active_sources": 14,
"healthy_sources": 12,
"warning_sources": 2,
"failed_sources": 0
},
"posts": {
"total_posts": 2540,
"unread_posts": 45,
"posts_today": 8,
"posts_this_week": 52,
"posts_this_month": 234
},
"categories": {
"total_categories": 5,
"categories_with_sources": 5
},
"performance": {
"sources_updated_today": 12,
"average_fetch_duration_ms": 1150,
"fastest_source_ms": 340,
"slowest_source_ms": 3200
},
"errors": {
"sources_with_errors": 2,
"total_consecutive_failures": 3,
"sources_in_backoff": 1
},
"generated_at": "2025-08-16T10:30:00Z",
"cache_duration": "5 minutes"
}
}Get a health overview of all RSS sources, highlighting problematic ones.
Endpoint: GET /api/v1/metrics/sources-health
Example Request:
GET /api/v1/metrics/sources-healthExample Response:
{
"message": "Sources health summary retrieved successfully",
"data": {
"summary": {
"total": 15,
"active": 14,
"inactive": 1,
"healthy": 12,
"warning": 2,
"failed": 0
},
"problematic_sources": [
{
"id": 7,
"name": "Slow Feed",
"status": "warning",
"consecutive_failures": 2,
"last_error_at": "2025-08-16T09:00:00Z",
"last_error_message": "Connection timeout",
"status_description": "Issues detected (2 recent failures)"
}
],
"generated_at": "2025-08-16T10:30:00Z"
}
}Get information about recent RSS processing activity.
Endpoint: GET /api/v1/metrics/recent-activity
Example Request:
GET /api/v1/metrics/recent-activityExample Response:
{
"message": "Recent processing activity retrieved successfully",
"data": {
"recent_posts_count": 28,
"recently_updated_sources": [
{
"source_id": 1,
"source_name": "Tech News",
"last_fetched_at": "2025-08-16T10:15:00Z",
"duration_ms": 890,
"status": "active",
"consecutive_failures": 0
}
],
"time_range": "Last 24 hours",
"generated_at": "2025-08-16T10:30:00Z"
}
}Get information about the last successful crawl and system health for display on the frontend.
Endpoint: GET /api/v1/metrics/crawl-status
Example Request:
GET /api/v1/metrics/crawl-statusExample Response:
{
"message": "Crawl status retrieved successfully",
"data": {
"last_successful_crawl": "2025-08-23T09:15:30Z",
"minutes_since_last_crawl": 45,
"is_recent": true,
"warning_threshold_minutes": 120,
"should_show_warning": false,
"human_readable": "45 minutes ago",
"status": "healthy"
}
}Response Fields:
last_successful_crawl- ISO timestamp of the most recent successful crawlminutes_since_last_crawl- Minutes elapsed since last successful crawlis_recent- Boolean indicating if crawl is within acceptable timeframewarning_threshold_minutes- System threshold for showing warnings (default: 120 minutes)should_show_warning- Boolean indicating if frontend should display warninghuman_readable- User-friendly time descriptionstatus- Overall status: "ok", "warning", "no_data", or "error"
Example Response (No Data):
{
"message": "Crawl status retrieved successfully",
"data": {
"status": "no_data",
"message": "No information available about the last crawl. The system may not have run a full crawl cycle yet since switching to the new tracking system.",
"last_crawl_at": null,
"minutes_ago": null,
"threshold_minutes": 80,
"needs_attention": false,
"human_readable": "No crawl data available"
}
}All API endpoints are subject to rate limiting:
- Default: 60 requests per minute per user
- Jobs API: 30 requests per minute per user (due to resource-intensive operations)
- Metrics API: 120 requests per minute per user (read-only operations)
Rate limit headers are included in all responses:
X-RateLimit-Limit: Total requests allowedX-RateLimit-Remaining: Remaining requestsX-RateLimit-Reset: Unix timestamp when the rate limit resets
200- Success201- Created (for POST operations)202- Accepted (for asynchronous job dispatch)400- Bad Request (validation errors)401- Unauthorized (missing or invalid token)403- Forbidden (insufficient permissions)404- Not Found422- Unprocessable Entity (validation failed)429- Too Many Requests (rate limited)500- Internal Server Error
{
"message": "Error description",
"errors": {
"field_name": ["Specific validation error"]
}
}- v1.0 - Initial API release with read operations and post management
- v1.1 - Added source and category management
- v1.2 - Added OPML import/export functionality
- v1.3 - Added Jobs API for background processing and Metrics API for observability
- v1.4 - Added crawl status endpoint for frontend health monitoring
Last Updated: August 16, 2025