Glossary

Why the Mastodon API scheduled_at Needs 5 Minutes of Lead Time

Taras Shynkarenko
Taras Shynkarenko
•Updated: •7 min read
Why the Mastodon API scheduled_at needs 5 minutes of lead timeWhy the Mastodon API scheduled_at needs 5 minutes of lead time

TL;DR, Quick Answer

7 min read

Mastodon documents scheduled_at as needing to be at least 5 minutes in the future, and the source backs it with MINIMUM_OFFSET = 5.minutes in ScheduledStatus. Anything nearer returns HTTP 422 with a validation error. A timestamp in the past behaves differently: PostStatusService drops it and publishes the status immediately. Two further limits, 300 scheduled statuses total and 25 per day, appear only in the source.

What is the minimum scheduled_at offset on the Mastodon API?

Every Mastodon API scheduled_at value has to land more than 5 minutes ahead of the server's clock, or POST /api/v1/statuses rejects the request with HTTP 422. The form-data documentation for that endpoint states it in one line: "Datetime at which to schedule a status. Providing this parameter will cause ScheduledStatus to be returned instead of Status. Must be at least 5 minutes in the future."

The update endpoint repeats the rule in slightly different words. PUT /api/v1/scheduled_statuses/:id documents its own scheduled_at as "Datetime at which the status will be published. Must be at least 5 minutes into the future", so moving an existing scheduled post nearer than five minutes fails the same way creating one does.

The number is not a documentation convention. It is a constant in the model, MINIMUM_OFFSET = 5.minutes.freeze in app/models/scheduled_status.rb, and the validation that uses it reads:

def validate_future_date
  errors.add(:scheduled_at, I18n.t('scheduled_statuses.too_soon')) if scheduled_at.present? && scheduled_at <= Time.now.utc + MINIMUM_OFFSET
end

The comparison is <=, so a timestamp exactly 5 minutes out fails. "At least 5 minutes" in the docs means strictly more than 5 minutes in the code.

What does the API return when the time is too near?

A 422 with a validation message in the error field. Mastodon's API error handling turns the Rails exception straight into JSON:

rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e|
  render json: { error: e.to_s }, status: 422
end

That concern lives in app/controllers/concerns/api/error_handling.rb and is included by Api::BaseController, so the body is the exception's own string rather than a machine-readable code. There is no error code, no field list and no retry-after hint; parsing it means matching on English text.

Why does the documented error string differ from the one servers send?

Because the locale string changed and the docs example did not. The scheduled_statuses page shows this 422 body for the update endpoint:

{
  "error": "Validation failed: Scheduled at The scheduled date must be in the future"
}

The current config/locales/en.yml on the mastodon/mastodon main branch carries a shorter phrase:

scheduled_statuses:
  over_daily_limit: You have exceeded the limit of %{limit} scheduled posts for today
  over_total_limit: You have exceeded the limit of %{limit} scheduled posts
  too_soon: date must be in the future

Rails prefixes the humanized attribute name, so a current server produces "Validation failed: Scheduled at date must be in the future". The documented example still carries the older "The scheduled date must be in the future" wording.

SourceString
docs.joinmastodon.org, scheduled_statuses 422 example"Validation failed: Scheduled at The scheduled date must be in the future"
config/locales/en.yml, main branch"Validation failed: Scheduled at date must be in the future"

Any client matching the documented sentence exactly will miss the error on a current server. Match on the 422 status and the presence of scheduled_at in your own request instead of on the sentence.

A person checks the time on their phone, echoing how a scheduled post's fate depends on a single clock reading.

What happens if scheduled_at is in the past?

The post goes out immediately, and no error is raised. This is the behaviour the documentation never mentions, and it lives in PostStatusService:

@scheduled_at = @options[:scheduled_at]&.to_datetime
@scheduled_at = nil if scheduled_in_the_past?

with

def scheduled_in_the_past?
  @scheduled_at.present? && @scheduled_at <= Time.now.utc
end

Setting @scheduled_at to nil makes scheduled? false, which routes the request through process_status! rather than schedule_status!. The response is a Status entity, not a ScheduledStatus, so a client that assumes scheduled_at always yields a ScheduledStatus will read a missing id for a post that is already public.

Three outcomes, one parameter:

AdaptlyPost
AdaptlyPost

Start Your 7-Day Free Trial

All-platform analytics

Social Inbox

AI-powered assistant

scheduled_at valueResultResponse
In the past, or exactly nowPublished immediatelyStatus
Within 5 minutes of nowRejected422, validation error
More than 5 minutes aheadQueuedScheduledStatus

The middle band is the trap. A retry loop that pushes a failed send "a couple of minutes later" walks into the 422; one that falls back to "post it now" by sending a past timestamp gets a live post instead of the error it expected.

The retry trap
Retry a few minutes later
  • Lands inside the 5-minute band
  • Server responds with 422
Fall back to "post it now"
  • Sends a past timestamp
  • PostStatusService drops scheduled_at
  • Status publishes immediately
Two common reactions to a failed send, and neither lands on the result the caller expected.

How many scheduled statuses can one account hold?

Two more limits sit next to the offset in the same model, and neither appears in the API documentation:

TOTAL_LIMIT = 300
DAILY_LIMIT = 25
MINIMUM_OFFSET = 5.minutes.freeze

TOTAL_LIMIT caps the number of scheduled statuses an account can have queued at once, and the message is "You have exceeded the limit of 300 scheduled posts". DAILY_LIMIT caps how many can share a single calendar date, validated with scheduled_at::date = ?::date against the database, and the message is "You have exceeded the limit of 25 scheduled posts for today".

Both arrive as the same 422 shape, with the message on base rather than on scheduled_at. A queue that loads a month of posts in one run will hit the 25-per-day wall long before the 300 total, and nothing on docs.joinmastodon.org warns about either.

What format does scheduled_at take?

An RFC 3339 datetime, which Mastodon documents separately as its datetime format: [YYYY]-[MM]-[DD]T[hh]:[mm]:[ss].[s][TZD], where the timezone designator is either Z for UTC or an offset such as +02:00. The docs are explicit that the separator is an uppercase T and that Z is always uppercase.

The validation compares against Time.now.utc on the server, not on the client. Client clock drift therefore eats directly into the five-minute margin, which is a good reason to schedule at six or seven minutes out rather than at five minutes and one second. The same failure mode shows up whenever a queue trusts local time, as it does across Mastodon and Bluesky and every other API that validates against its own clock.

A wall calendar on a desk stands in for the queue of scheduled posts an account manages over time.

How do you change or cancel a scheduled status?

Three endpoints manage the queue after creation. GET /api/v1/scheduled_statuses lists them, defaulting to 20 results with a maximum of 40. PUT /api/v1/scheduled_statuses/:id takes a new scheduled_at, subject to the same 5-minute rule. DELETE /api/v1/scheduled_statuses/:id cancels one and returns an empty object.

Changing the text is a different operation. Mastodon notes that PUT /api/v1/statuses/:id edits a published status, and that "To edit the scheduled_at attribute of a ScheduledStatus to change the publication date, use the scheduled status endpoint." The content of a queued post lives in the params object of the ScheduledStatus entity and the update endpoint accepts only the date, so changing wording means deleting and recreating.

A queued status that no longer exists returns 404 with {"error": "Record not found"}, which is also what a scheduled status belonging to another account returns.

What does the 5-minute floor mean for a posting queue?

It sets the shortest useful gap between "decide to post" and "post". Anything under five minutes has to be sent as an immediate status rather than a scheduled one, so a scheduler needs a branch, not a single code path: below the threshold, drop scheduled_at and publish; above it, queue and store the returned ScheduledStatus id.

Two other numbers belong in the same planning pass. Mastodon's default post length is 500 characters, covered in the Mastodon character limit, and per-server rate limiting sits on top of all of it, in the same way Bluesky publishes its own API rate limits separately from its post rules. Building the calendar around the platform floor rather than around a tool's own preferences is the general habit behind scheduling social media posts reliably.

One caveat covers every number above. The constants come from the mastodon/mastodon main branch, and a server running a fork or an older release can carry different values, so read the 422 body rather than assuming 5, 25 and 300 hold everywhere.

Frequently asked questions

Does scheduled_at accept a time exactly 5 minutes from now?

No. The validation compares with <=, so scheduled_at <= Time.now.utc + 5.minutes fails. The documented phrase "at least 5 minutes in the future" means strictly more than five minutes once you read the source.

What HTTP status does Mastodon return for a scheduled_at that is too soon?

  1. Mastodon rescues ActiveRecord::RecordInvalid and renders { error: e.to_s } with status 422, so the body is a plain English validation sentence rather than a structured error object.

Which Mastodon version added scheduled_at?

2.7.0. The version history on the POST /api/v1/statuses documentation lists "2.7.0 - scheduled_at added", and the three scheduled_statuses endpoints were added in the same release.

AdaptlyPost
AdaptlyPost

Start Your 7-Day Free Trial

All-platform analytics

Social Inbox

AI-powered assistant

Does a scheduled post return a Status or a ScheduledStatus?

A ScheduledStatus, whenever scheduled_at is accepted. Mastodon states that the endpoint "Returns: Status. When scheduled_at is present, ScheduledStatus is returned instead." A past timestamp is the exception, because the server drops the parameter and publishes immediately, returning a Status.

Can you schedule more than 25 Mastodon posts for the same day?

Not on a default server. DAILY_LIMIT = 25 in app/models/scheduled_status.rb counts existing scheduled statuses sharing the same date and rejects the twenty-sixth with "You have exceeded the limit of 25 scheduled posts for today". A separate TOTAL_LIMIT = 300 caps the whole queue. Neither number appears in the API documentation.

Where is the 5-minute minimum documented?

On the statuses API methods page at docs.joinmastodon.org/methods/statuses/, in the scheduled_at form-data parameter, and again on docs.joinmastodon.org/methods/scheduled_statuses/ for the update endpoint. The constant behind it is MINIMUM_OFFSET = 5.minutes.freeze in app/models/scheduled_status.rb in the mastodon/mastodon repository.

How many results does GET /api/v1/scheduled_statuses return by default?

Twenty. The endpoint defaults to 20 scheduled statuses per page and caps at 40, so an account sitting near the 300-total limit needs several requests to page through its whole queue.

What happens if you try to fetch a canceled scheduled post?

The server returns 404 with {"error": "Record not found"}. That's the same response a scheduled status belonging to another account returns, so a 404 here doesn't tell you whether the post never existed, was already canceled, or simply isn't yours.

Can you edit the text of a scheduled Mastodon post?

Editing the text means deleting the scheduled post and creating a new one, not modifying it in place. The update endpoint only accepts a new scheduled_at, and the content of a queued post lives in the params object of the ScheduledStatus entity.

How far in advance should you actually schedule a Mastodon post?

More than five minutes, but six or seven minutes is safer than five minutes and one second. The validation runs against the server's clock rather than the client's, so any drift in local time eats directly into that margin.

Was This Article Helpful?

Let us know what you think!

See us more often in Google

One click marks AdaptlyPost as a preferred source, so our articles sit higher in your Top Stories, AI Mode, and AI Overviews.

Before you go...

AdaptlyPost

AdaptlyPost

Schedule your content across all platforms

Manage all your social media accounts in one place with AdaptlyPost.

All-platform analytics

Social Inbox

AI-powered assistant

Related Glossary Terms

Related Articles