> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tuco.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Reply Hours

> Read, set and clear the days and times a goal bot is allowed to answer a lead. Outside them the bot sends nothing and a person answers instead.

<Note>
  **Goal bots and task agents are the same object.** `/api/goal-bots/{id}/reply-hours` and
  `/api/tasks/{id}/reply-hours` are aliases over one collection and behave identically.
</Note>

Each bot has its **own** hours. There is no workspace-wide schedule, so exactly one schedule
decides any one reply.

**A bot with no schedule answers at all times.** That is every bot until somebody sets one, so
adding reply hours is the only thing that can ever make a bot quieter.

The same field is editable through [`PATCH /api/goal-bots/{id}`](/api-reference/endpoint/update-goal-bot)
as `replyHours`. Prefer this route for anything automated: it sends only the schedule, so a stale
`prompt` in your payload cannot overwrite one somebody edited in the dashboard while your script
was running.

***

## What happens outside the hours

The bot **sends nothing and drafts nothing**.

The owner still gets told. They receive the **ordinary new-reply email and Telegram** — the same
ones a workspace with no bot at all gets for an inbound. No empty draft card, no handoff alert,
nothing bot-shaped. Only the bot's work stops; the alerting is unchanged.

<Warning>
  **Nothing is queued.** The message is not answered later when the window reopens. There is no
  catch-up job and no retry. From that point the conversation is manual — a person answers it, or
  nobody does.
</Warning>

***

## Authentication

Pass your workspace API key as a Bearer token, or use a Clerk session token.

```bash theme={null}
Authorization: Bearer tuco_xxxxxxxxxxxxx
```

***

## Path parameter

<ParamField path="id" type="string" required>
  The bot id. A malformed id returns `400 Invalid id`.
</ParamField>

***

## The schedule object

```json theme={null}
{
  "days": {
    "mon": { "start": "09:00", "end": "17:00" },
    "sat": { "start": "10:00", "end": "14:00" }
  },
  "override": null
}
```

<ParamField body="days" type="object">
  A day that is **present** is a day the bot answers. A day that is **absent** is a day it does
  not. Keys are `mon` `tue` `wed` `thu` `fri` `sat` `sun`. An empty object `{}` means no schedule
  at all, so the bot answers always.
</ParamField>

<ParamField body="days.{day}.start" type="string" required>
  `"HH:MM"`, 24-hour.
</ParamField>

<ParamField body="days.{day}.end" type="string" required>
  `"HH:MM"`, 24-hour. **Exclusive** — `09:00`–`17:00` answers at 16:59 and does not answer at 17:00.

  An end **earlier** than the start is a **night shift**, not an error. See below.
</ParamField>

<ParamField body="override" type="string | null">
  `"on"` answers at all times. `"off"` never answers. `null` follows `days`.

  An override beats the schedule and does **not** erase it — clear the override and your saved
  days apply again. **It has no expiry**: it holds until somebody changes it, so one set on a
  Friday is still set on Monday.
</ParamField>

### Night shifts

Set an end time **earlier** than the start time and the window runs past midnight.

The hours belong to the day the shift **starts**. `20:00`–`08:00` on `mon` covers Monday evening
**and Tuesday until 08:00** — so a lead writing at 02:00 on Tuesday is answered, because Monday's
shift has not finished, and one writing at 09:00 on Tuesday is not.

```json theme={null}
{ "days": { "mon": { "start": "20:00", "end": "08:00" } } }
```

For a bot that answers every night, set the shift on each day it starts:

```json theme={null}
{
  "days": {
    "mon": { "start": "20:00", "end": "08:00" },
    "tue": { "start": "20:00", "end": "08:00" },
    "wed": { "start": "20:00", "end": "08:00" },
    "thu": { "start": "20:00", "end": "08:00" },
    "fri": { "start": "20:00", "end": "08:00" }
  }
}
```

That answers Monday night through Saturday 08:00, and stays quiet in the daytime gap between
shifts.

### Rules the API enforces

* `start` and `end` must **differ**. The same value for both is refused, because it cannot be
  told apart from "no time at all" or "the whole day".
* Times are read in the **workspace timezone**, never the server's and never the lead's. `GET`
  tells you which one.
* A bad schedule is rejected **whole**. Nothing is stored, and the message names the day.

***

## `GET` — read it

```bash theme={null}
curl "https://app.tuco.ai/api/goal-bots/6aa9813105bb2cbf5931ac1f/reply-hours" \
  -H "Authorization: Bearer tuco_xxxxxxxxxxxxx"
```

### Success (`200 OK`)

```json theme={null}
{
  "goalBotId": "6aa9813105bb2cbf5931ac1f",
  "name": "Auto Replies",
  "replyHours": { "days": { "mon": { "start": "09:00", "end": "17:00" } } },
  "timezone": "America/New_York",
  "answeringNow": true,
  "reason": "inside",
  "localTime": "11:02",
  "localDay": "mon",
  "summary": "Monday, 09:00 to 17:00. Tuesday and Wednesday and Thursday and Friday and Saturday and Sunday, no replies."
}
```

`answeringNow` and `reason` answer the question a script actually has before it does anything, so
you do not have to re-implement the schedule logic yourself.

| `reason` | meaning |
| - | - |
| `no_schedule` | no hours set — the bot answers always |
| `inside` | inside today's window |
| `outside` | today has a window, and now is not in it |
| `day_off` | the bot does not answer today |
| `override_on` | an override is forcing it to answer |
| `override_off` | an override is forcing it silent |

***

## `PUT` — set it

`PUT` **replaces** the whole schedule. It does not merge — send Monday to Friday and you get
Monday to Friday, with no Saturday left over from last month.

```bash theme={null}
curl -X PUT "https://app.tuco.ai/api/goal-bots/6aa9813105bb2cbf5931ac1f/reply-hours" \
  -H "Authorization: Bearer tuco_xxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
        "days": {
          "mon": { "start": "09:00", "end": "17:00" },
          "tue": { "start": "09:00", "end": "17:00" },
          "wed": { "start": "09:00", "end": "17:00" },
          "thu": { "start": "09:00", "end": "17:00" },
          "fri": { "start": "09:00", "end": "17:00" }
        }
      }'
```

Stop the bot now and keep the schedule for when you turn it back on:

```bash theme={null}
curl -X PUT "https://app.tuco.ai/api/goal-bots/6aa9813105bb2cbf5931ac1f/reply-hours" \
  -H "Authorization: Bearer tuco_xxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "days": { "mon": { "start": "09:00", "end": "17:00" } }, "override": "off" }'
```

The body may be the schedule itself or wrapped as `{ "replyHours": { ... } }` — both read
naturally, so both are accepted.

### Success (`200 OK`)

```json theme={null}
{
  "ok": true,
  "goalBotId": "6aa9813105bb2cbf5931ac1f",
  "replyHours": { "days": { "mon": { "start": "09:00", "end": "17:00" } } },
  "timezone": "America/New_York",
  "answeringNow": true,
  "reason": "inside",
  "summary": "Monday, 09:00 to 17:00. Tuesday and Wednesday and Thursday and Friday and Saturday and Sunday, no replies."
}
```

***

## `DELETE` — clear it

The bot goes back to answering at all times.

```bash theme={null}
curl -X DELETE "https://app.tuco.ai/api/goal-bots/6aa9813105bb2cbf5931ac1f/reply-hours" \
  -H "Authorization: Bearer tuco_xxxxxxxxxxxxx"
```

`PUT` with a body of `null` or `{}` does the same thing.

***

## Errors

A bad schedule is rejected whole, and the message names the day so you know which value to fix.

```json theme={null}
{
  "error": "Saturday: the start time and the end time are the same. Set an end time that is not the start time.",
  "errors": ["Saturday: the start time and the end time are the same. Set an end time that is not the start time."]
}
```

| Status | When | Body |
| - | - | - |
| `400` | `start` equal to `end` | `{{ "error": "Saturday: the start time and the end time are the same. Set an end time that is not the start time." }}` |
| `400` | A time that is not `HH:MM` | `{{ "error": "Monday: the start time must look like 09:00." }}` |
| `400` | A key that is not a day | `{{ "error": "\"funday\" is not a day. Use mon, tue, wed, thu, fri, sat or sun." }}` |
| `400` | Malformed `{id}` | `{{ "error": "Invalid id" }}` |
| `401` | Missing or invalid API key | `{{ "error": "Unauthorized" }}` |
| `404` | No bot with that id in your workspace | `{{ "error": "Goal bot not found" }}` |

***

## Gotchas

<Warning>
  **Hours apply to the bot that ANSWERS, which is not always the bot you set them on.** Routing
  happens first, hours second. A bot with an empty `includeTags` answers every lead **before** the
  default "Auto Replies" bot does — so hours on the default bot do nothing in a workspace that also
  has an untagged bot. Check which bot actually answers a lead before you schedule it.
</Warning>

* **There is no workspace default.** Setting hours on one bot does nothing to the others.
* **Instruction bots have no reply hours.** The older stage engine on `/api/instructions` is not
  covered, so "the AI stops at 17:00" is only true of goal bots.
* A schedule stored with a broken window — backwards, or not `HH:MM` — makes the bot **answer**,
  not go silent. A bug must never be able to mute a bot that was working.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.