# Geo SMS Hub > A developer-first transactional SMS API for Georgia. It sends one application-triggered message and exposes its honest normalized status. It is in private beta and is not a campaign, bulk-SMS, or two-way messaging product. ## Canonical documentation - [API reference](API.md): Public endpoints, request and response shapes, status semantics, errors, retry safety, fake-provider test scenarios, and local setup. - [README](README.md): Repository overview and short quickstart. - [Project context](PROJECT.md): Audience, positioning, product boundaries, and beta goals. When these repository files are published, serve this file at `/llms.txt` on the documentation domain and make the API reference available at a stable public URL. ## Agent integration rules 1. The public API is three routes: `POST /v1/test-keys`, `POST /v1/messages`, and `GET /v1/messages/{id}`. Do not invent others: live keys and credits are managed by a human on the dashboard, and there are no endpoints for projects, senders, webhooks, campaigns, balance, or billing. 2. Authenticate with `Authorization: Bearer `. If you have no key, get one instantly with `POST /v1/test-keys` — no authentication, no account, no browser. It returns an `sk_test_…` key that always uses the fake provider and never sends a real SMS or spends credits, so you can build and verify a complete integration unattended. The same response carries a `claim_url`: give it to your human to open, sign in, and confirm, which enables live delivery — and the key you are already using keeps working. 3. `POST /v1/messages` requires an `Idempotency-Key` header. Generate one per logical message and reuse it only to retry the identical request body. 4. The recipient must exactly match `+9955` followed by eight digits. Do not normalize other formats or countries on the client’s behalf. 5. Send `{ "to": "+995591234567", "template": "verification_code", "variables": { "code": "482913" } }`, optionally with `"locale": "en"` (default `ka`, Georgian). There is no `text` field and no `from` field: message text always comes from a template, and messages go out under the shared sender `GeoAuth` unless the team has approved another sender for the project. 6. `201` means a message resource was created, not necessarily delivered. `submitted` means provider acceptance only. `delivered` and `undeliverable` are the only terminal delivery outcomes. 7. A `503 provider_unavailable` is retry-safe. A `201` message with `status: "unknown"` is not safe to retry automatically because a provider submission may already have happened. 8. Responses never include message text, a full phone number, raw provider values, or provider message IDs. Persist the context your application needs before the call. 9. Templates: `verification_code` (`code`), `login_code` (`code`), `password_reset_code` (`code`, `minutes`). Every variable value must be a JSON string. `code` is 4 to 8 digits; `minutes` is 1 to 999. Values that look like a link, a domain, or a phone number are refused with `invalid_variables`. The full wording of each template is in the API reference. The response reports `segments`, the unit credits are charged in. 10. Live sends spend credits, one per segment; test keys are never charged or limited. `402 insufficient_credits` means the project needs credits from its human owner. `429 daily_limit_reached` means the free tier's daily limit is used up: do not retry before the reset time in the message. Nothing is sent in either case. 11. Test magic recipients: `+995500000001` → rejected; `+995500000002` → retry-safe 503; `+995500000003` → ambiguous `unknown`; `+995500000004` → later `undeliverable`; another valid number → later `delivered`. ## API discovery The interactive API documentation is at `/docs`; the OpenAPI document is at `/openapi.json`. The production-beta base URL is `https://api-production-d007.up.railway.app`, and the local default is `http://127.0.0.1:8000`.