Partner Publish API (Hope)
Server-to-server endpoint for partner apps to publish a live article and matching post card in one call. No Kind Joe staff session required.
Status
Implemented at POST /api/partner/publish. Deploy with HOPE_PUBLISH_API_KEY set in production.
Endpoints
POST /api/partner/upload-image— store the photo and get the hero and square linksPOST /api/partner/publish— create the live article
Authentication
Same secret on Kind Joe and Hope (HOPE_PUBLISH_API_KEY). Use either:
Authorization: Bearer <HOPE_PUBLISH_API_KEY>x-api-key: <HOPE_PUBLISH_API_KEY>
Upload an image first
Same API key. Send a public https image URL, a data:image/...;base64,... URI, or a multipart field named file (max 8MB). Kind Joe uploads it to Cloudinary and returns both crops. Paste those two URLs into the publish body. You do not need your own Cloudinary keys.
curl -X POST "$KINDJOE_API_BASE_URL/api/partner/upload-image" \
-H "Authorization: Bearer $HOPE_PUBLISH_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "imageUrl": "https://example.com/storm-photo.jpg" }'{
"success": true,
"originalUrl": "https://res.cloudinary.com/…/kindjoe/articles/storm.jpg",
"coverImageUrl": "https://res.cloudinary.com/…/c_fill,g_auto,w_1600,h_900,…",
"postCard": { "imageUrl": "https://res.cloudinary.com/…/c_fill,g_auto,w_800,h_800,…" }
}Request body
{
"slug": "my-article-slug",
"title": "Article title",
"excerpt": "At least twelve words in the excerpt for validation.",
"contentMarkdown": "# Full markdown body…",
"coverImageUrl": "https://…",
"category": "Local News",
"tags": ["ventura-county"],
"seo": {
"title": "SEO title",
"description": "Meta description — at least 70 characters required.",
"sourceUrl": "https://original-source",
"sourceArticleId": 12345,
"sourceType": "news_rss_articles"
},
"postCard": {
"imageUrl": "https://res.cloudinary.com/…/tile.jpg",
"caption": "Optional"
},
"isVentura": true
}Response (201)
{
"success": true,
"articleId": "…",
"slug": "my-article-slug",
"articleUrl": "https://www.kindjoe.com/my-article-slug",
"destinationUrl": "https://www.kindjoe.com/my-article-slug",
"postId": 42,
"feed": "ventura",
"authorId": "cms-writer-id"
}What Kind Joe does
| Rule | Behavior |
|---|---|
| Publish | Live article (isDraft: false, publishedAt = now) |
| Author | Random CMS Writer or Editor (not system-ai / rss-auto-writer) |
| Ventura | tag ventura-county, post card in ventura_posts. Public URL stays /{slug} |
| KindJoe | /{slug}, row in posts |
| Post card | destinationUrl = article URL (same as admin post modal) |
Hope env (your app)
Image upload uses Kind Joe's Cloudinary. Hope only needs the shared publish key.
KINDJOE_API_BASE_URL=https://www.kindjoe.com
HOPE_PUBLISH_API_KEY=<same secret as Kind Joe>Quick test
curl -X POST "$KINDJOE_API_BASE_URL/api/partner/publish" \
-H "Authorization: Bearer $HOPE_PUBLISH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"slug":"hope-test-…","title":"Test","excerpt":"Twelve word minimum excerpt for validation.","contentMarkdown":"# Test","seo":{"description":"Seventy character minimum meta description for publish validation on Kind Joe."},"postCard":{"imageUrl":"https://res.cloudinary.com/demo/image/upload/sample.jpg"}}'Related
- KindJoe post cards — feed tile model
- Read-only RSS pool: see markdown doc
docs/developers/rss-news-read-api.mdin the repo
Errors
401— invalid/missing API key400— validation (excerpt, SEO, body)409— slug conflict