Takes the same body as creating a post and runs every check (text length and media per platform, accounts, schedule, queue) without storing anything. Answers 200 with valid: false and the problems instead of an error, so agents can fix everything in one round.
Requires posts:read.
Authorization
bearerAuth API key starting with fh_
In: header
Header Parameters
Any unique string up to 255 characters. A retry with the same key within 24 hours returns the first response instead of running again.
length <= 255Request Body
application/json
Profile id
^prof_[0-9a-fA-F]{32}$Post text. Each account can get its own text through targets
length <= 63206Publish to every active account of these platforms in the profile
1 <= itemsOr name the accounts explicitly (wins over platforms)
1 <= itemsThe accounts to publish to, each with optional own text. Wins over platforms and accountIds
1 <= items <= 50Files from POST /media, in display order
items <= 20Schedule for this time. Leave out all three timing fields for a draft
date-timePublish right away
Take the profile's next free queue slot
Time zone shown with the post. Defaults to the profile's
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://feedhorizon.com/api/v1/posts/validate" \ -H "Content-Type: application/json" \ -d '{ "profileId": "prof_0195f0a27b3c7d4e8f5a1b2c3d4e5f60" }'{
"data": {
"valid": true,
"errors": [
{
"code": "string",
"message": "string",
"param": "string",
"accountId": "acc_0195f0a27b3c7d4e8f5a1b2c3d4e5f60",
"platform": "twitter"
}
],
"warnings": [
{
"code": "string",
"message": "string",
"param": "string",
"accountId": "acc_0195f0a27b3c7d4e8f5a1b2c3d4e5f60",
"platform": "twitter"
}
],
"scheduledAt": "2026-11-10T08:00:00.000Z",
"targets": [
{
"accountId": "acc_0195f0a27b3c7d4e8f5a1b2c3d4e5f60",
"platform": "twitter"
}
]
}
}{
"error": {
"type": "invalid_request_error",
"code": "invalid_json",
"message": "The body is not valid JSON",
"docUrl": "https://feedhorizon.com/docs/api/errors#invalid_request_error",
"requestId": "req_m2x81a0kfd3h9s"
}
}{
"error": {
"type": "authentication_error",
"code": "missing_api_key",
"message": "No Authorization: Bearer header",
"docUrl": "https://feedhorizon.com/docs/api/errors#authentication_error",
"requestId": "req_m2x81a0kfd3h9s"
}
}{
"error": {
"type": "permission_error",
"code": "insufficient_scope",
"message": "The key lacks the scope for this endpoint",
"docUrl": "https://feedhorizon.com/docs/api/errors#permission_error",
"requestId": "req_m2x81a0kfd3h9s"
}
}{
"error": {
"type": "idempotency_error",
"code": "idempotency_request_in_progress",
"message": "The first request with this key is still running. Retry in a few seconds",
"docUrl": "https://feedhorizon.com/docs/api/errors#idempotency_error",
"requestId": "req_m2x81a0kfd3h9s"
}
}{
"error": {
"type": "idempotency_error",
"code": "idempotency_key_reused",
"message": "The key was used for a different request. Use a new key",
"docUrl": "https://feedhorizon.com/docs/api/errors#idempotency_error",
"requestId": "req_m2x81a0kfd3h9s"
}
}{
"error": {
"type": "rate_limit_error",
"code": "rate_limited",
"message": "Too many requests. Wait the seconds in Retry-After",
"docUrl": "https://feedhorizon.com/docs/api/errors#rate_limit_error",
"requestId": "req_m2x81a0kfd3h9s"
}
}{
"error": {
"type": "api_error",
"code": "internal_error",
"message": "Something failed on our side. Retry with backoff, using the same Idempotency-Key for writes",
"docUrl": "https://feedhorizon.com/docs/api/errors#api_error",
"requestId": "req_m2x81a0kfd3h9s"
}
}Create a post POST
Creates a draft, a scheduled post, a queued post or publishes right away, depending on which timing field you send. Use `targets` to give single accounts their own text. The post is checked against every platform first; all problems come back at once in `error.details` (try `POST /posts/validate` for a dry run). Send an `Idempotency-Key` header to make retries safe. Requires `posts:write`.
Update a post PATCH
Changes a post that has not started publishing. `scheduledFor: null` turns it back into a draft. Sending targets, accountIds or platforms replaces the target accounts. Requires `posts:write`.