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

> ## Agent Instructions
> For chatbots and coding agents, start at /guides/ai-agents.
> Hosted MCP: POST https://api.robase.dev/mcp (Streamable HTTP). Auth: Authorization: Bearer robe_… or robe_agent_….
> Phase 1 tools: get_pricing, send_otp, verify_otp, get_otp, send_sms, get_sms. No payment or top-up tools.
> Call get_pricing; treat listed:true as marketed markets (NG, GH, KE, ZA, GB, BJ, CI, US). Do not invent countries or delivery percentages.
> Prefer OTP-shaped tests such as "Your Robase test OTP is 123456". Do not send Hello or Good morning as tests. Match errors on error.type.

# SMS logs and OTP logs

> Filters, statuses, retries, and how to debug a failed first send.

[SMS logs](https://robase.dev/app/sms-logs) lists transactional SMS. [OTP logs](https://robase.dev/app/otp-logs) lists every OTP from the workspace. Both show 25 rows per page, in the workspace timezone. The API timestamps are UTC.

Anyone with `logs.read` can open them. Every role has that permission. See [Team](https://docs.robase.dev/dashboard/team.md).

## Filters

Both pages filter the same way. The choices live in the address, so a refresh keeps them.

| Filter | SMS logs | OTP logs |
| - | - | - |
| Status | All, Sent, Delivered, Pending, Failed, Blocked | All, Sent, Verified, Pending, Failed, Expired |
| Phone | Search box. Applied when you submit it. | Same |
| Live tail | Reloads the current page every 5 seconds | Same |
| Phone display | The message body is in the row | Phones are masked until you choose **Reveal phones** |

**Reset** clears the status and the phone search.

A link that includes the message id opens that row's drawer even when the message is older than the first page. Closing the drawer takes the id back out of the address.

The empty state points at the overview, which is where a first test is sent. See [Test send](https://docs.robase.dev/dashboard/test-send.md).

## Statuses

The badge is the message status. The drawer adds a timeline. A hop appears only when its time is known. A `failed` or `blocked` hop includes a stable `reason` and a `refunded` flag read from the credit ledger.

SMS statuses you can filter:

| Status | What it means |
| - | - |
| Pending | Accepted and not yet in a later state |
| Sent | Handed to the network |
| Delivered | The network reported it delivered |
| Failed | Not delivered |
| Blocked | We stopped it. This is not a carrier failure. The credit is returned. |

OTP statuses you can filter:

| Status | What it means |
| - | - |
| Pending | Accepted, not yet in a later state |
| Sent | Handed on |
| Verified | The code matched |
| Failed | Not delivered, or stopped |
| Expired | The code is past its TTL |

An OTP timeline can also show **delivered** when that time is known. Delivered is not one of the OTP filter chips.

Reason tokens on a hop:

| Reason | Meaning |
| - | - |
| `send_failed` | Robase could not send the message |
| `delivery_failed` | The network reported it undelivered |
| `antispam_blocked` | The anti-spam screen stopped it |
| `antispam_flagged` | Screened as spam, and still sent |
| `kyc_required` | Stopped for business verification. See [KYC](https://docs.robase.dev/dashboard/kyc.md) |
| `workspace_suspended` | The workspace was suspended after the message was accepted |
| `workspace_restricted` | The workspace entered the 48 hour pause after the message was accepted |
| `max_attempts_exceeded` | The OTP used its verify budget |

`refunded: true` means the credit ledger has the refund. Do not infer a refund from the status alone.

## Retries

A retry is another attempt at the same message after the network confirms it failed. The body stays the same. The customer is charged once. The extra attempt uses the other route on that destination, when there is one.

Retries are an operator switch. A fresh install ships with them **off** (`message_retry_enabled` is false). While they are off, a failed delivery is failed, and the refund rules for that failure apply. There is no customer control to turn retries on.

When an operator has turned them on:

* The failure has to arrive inside the window, measured from when the message was created. The packaged window is 2 minutes. An operator can set it from 15 seconds to 10 minutes.
* The packaged extra-attempt count is 1. The ceiling is 5.
* A destination with a single route is not retried. There is no other route to try.
* An OTP is retried only while the code is still unexpired. Re-sending an expired code is refused.
* Attempts alternate between the two routes.

Do not tell a customer a failed send will retry. Say it retries only when that switch is on, inside the window, and a second route exists.

## Debug a failed first send

A first send uses real credits and a real phone. A new account has **0 credits** until the first successful top-up. Marketing content is blocked with **403** `content_blocked`. Transactional SMS without the workspace's own approved sender ID returns **422** `sender_id_required`.

Work down this list.

1. **Did the API accept it?** If the overview or the API returned an error and the logs have no row, match `error.type`. Nothing was charged for these refusals:

   | `error.type` | What to do |
   | - | - |
   | `insufficient_credits` | Top up on [Billing](https://robase.dev/app/billing). The first successful top-up adds 10 bonus credits, once per account. |
   | `sender_id_required` | Request a sender ID. OTP can go out under the Robase name. See [Sender IDs](https://docs.robase.dev/sms/sender-ids.md). |
   | `content_blocked` | Send OTP or a transactional message. Marketing content is refused. |
   | `kyc_required` | Finish [business verification](https://docs.robase.dev/dashboard/kyc.md). |
   | `invalid_phone` | Use E.164, for example `+2348012345678`. |
   | `country_not_supported` | That destination has no SMS route. |

   The full list is on [Errors](https://docs.robase.dev/concepts/errors.md).

2. **Find the row.** Open SMS logs or OTP logs. Filter **Failed**. Search the phone. Open the row. If the first test was an OTP, start with OTP logs. The overview test card sends an OTP.

3. **Read the timeline.** Use the reason table above. `delivery_failed` means the network reported it undelivered. `send_failed` means we did not hand it on. `refunded` tells you whether the credit came back.

4. **Check the phone and the balance again** before you send a second test. A second test spends credits again. It is a new message, not a free replay of the first.

5. **Still stuck.** Open a ticket from [Support](https://robase.dev/app/support) with the message id, the phone, and the time. Do not paste API keys. See [Support tickets](https://docs.robase.dev/dashboard/support.md).


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