Skip to content

Commit d62c2ad

Browse files
committed
fix: validate empty api key, correct geocode and timezone-union docs, add FAQ
1 parent 9c3fb82 commit d62c2ad

3 files changed

Lines changed: 47 additions & 9 deletions

File tree

AGENTS.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ roxy, err := roxyapi.NewRoxy(key, roxyapi.WithHTTPClient(&http.Client{Timeout: 1
2727
## Rules to get right
2828

2929
- **Methods are grouped by domain and named for the spec operation id.** `roxy.Astrology.GenerateNatalChart(...)`, `roxy.VedicAstrology.GenerateBirthChart(...)`. Never invent a name; the full list is in `docs/llms-full.txt`.
30-
- **Argument order is `(ctx, pathParams..., params, body)`, but the arity varies.** `params` is a nilable `*XxxParams` of query parameters (it carries `Lang` on i18n endpoints); pass `nil` for none. POST endpoints add a typed `body` last. **An endpoint with no query parameters has NO `params` argument at all** (for example `roxy.Usage.GetUsageStats(ctx)` and `roxy.Languages.ListLanguages(ctx)`). Do not pass a stray `nil` to those: `nil` would be read as a request editor and panic. When unsure, let autocomplete show the signature.
30+
- **Argument order is `(ctx, pathParams..., params, body)`, but the arity varies.** `params` is a nilable `*XxxParams` of query parameters (it carries `Lang` on i18n endpoints); pass `nil` for none. POST endpoints add a typed `body` last. **An endpoint with no query parameters has NO `params` argument at all** (for example `roxy.Usage.GetUsageStats(ctx)` and `roxy.Languages.ListLanguages(ctx)`). Do not pass a stray `nil` to those: it COMPILES (the last arg is variadic) then PANICS at runtime in applyEditors. Call them with `ctx` only. When unsure, let autocomplete show the signature.
3131
- **The request body type is always `roxyapi.<MethodName>JSONRequestBody`** (some are aliases of a named request like `NatalChartRequest`; both names work). Build it as a struct literal.
3232
- **Read the success body from `JSON200`** (a typed struct, nil unless the call was a 2xx): `resp.JSON200.Cities[0].Latitude`. `resp.StatusCode()` and `resp.Bytes()` give the raw response.
3333
- **Handle errors with `errors.As` on `*RoxyError`.** Switch on `Code` (stable), not `Message`. On a 400, range over `Issues`.
@@ -39,7 +39,7 @@ Every chart, horoscope, panchang, dasha, dosha, navamsa, KP, synastry, compatibi
3939

4040
```go
4141
search, err := roxy.Location.SearchCities(ctx, &roxyapi.SearchCitiesParams{Q: "Berlin Germany"})
42-
if err != nil {
42+
if err != nil || len(search.JSON200.Cities) == 0 { // a 200 can still return zero cities
4343
return err
4444
}
4545
city := search.JSON200.Cities[0] // fields: City, Country, Latitude, Longitude, Timezone (IANA), UtcOffset, Population
@@ -55,7 +55,7 @@ chart, err := roxy.Astrology.GenerateNatalChart(ctx, nil, roxyapi.NatalChartRequ
5555
})
5656
```
5757

58-
`Q` accepts a bare city (`"Paris"`), city plus country (`"Berlin Germany"`), or comma qualified (`"Springfield, Illinois"`). Use the qualified form to disambiguate.
58+
`Q` accepts a bare city (`"Paris"`), city plus country (`"Berlin Germany"`), or comma qualified (`"Springfield, Illinois"`). Use the qualified form to disambiguate, with a full country name, not an abbreviation (`"London, United Kingdom"`, not `"London, UK"`).
5959

6060
## Domains
6161

@@ -105,9 +105,12 @@ import (
105105
roxyapi "github.com/RoxyAPI/sdk-go"
106106
)
107107

108-
// The Timezone field is a per-request union. Every timezone-taking request R exposes
109-
// the type R_Timezone with two builders: FromRTimezone0(decimalOffset) and
110-
// FromRTimezone1(ianaName). Both return an error you can ignore for a static value.
108+
// The Timezone field is a per-request union. Build it with the generated From..0
109+
// (decimal offset) or From..1 (IANA) method. The union TYPE NAME is not always
110+
// guessable: a $ref body uses <Request>_Timezone (NatalChartRequest_Timezone); an
111+
// inline body (most POST endpoints) uses <Operation>JSONBody_Timezone, e.g.
112+
// GenerateBodygraphJSONBody_Timezone. If unsure, write the field with any value and
113+
// read the expected type from the compiler error, or use autocomplete.
111114
var tz roxyapi.NatalChartRequest_Timezone
112115
_ = tz.FromNatalChartRequestTimezone1("Europe/Berlin") // or .FromNatalChartRequestTimezone0(1)
113116

@@ -190,7 +193,10 @@ LLMs hallucinate confidently here. The specific traps:
190193

191194
## Go-specific gotchas
192195

193-
- **Some methods have no `params` argument** (see Rules). Passing `nil` to those panics. Affected: `Usage.GetUsageStats`, `Languages.ListLanguages`, `Crystals.ListCrystalColors`, `Crystals.ListCrystalPlanets`, `Dreams.GetSymbolLetterCounts`.
196+
- **Some methods have no `params` argument** (see Rules). Passing `nil` to those compiles but PANICS at runtime (the trailing arg is a variadic request editor). Affected: `Usage.GetUsageStats`, `Languages.ListLanguages`, `Crystals.ListCrystalColors`, `Crystals.ListCrystalPlanets`, `Dreams.GetSymbolLetterCounts`.
197+
- **`Timezone` union type names vary:** `<Request>_Timezone` for a named body, `<Operation>JSONBody_Timezone` for an inline body (most POST endpoints). Cannot guess it? Write the field with any value and read the expected type from the compiler error, or use autocomplete.
198+
- **`NewRoxy` returns `*roxyapi.Roxy`** (the type for your own function signatures and struct fields) and returns an error on an empty API key, so a missing `ROXYAPI_KEY` fails at construction, not as a confusing later 401.
199+
- **A successful `SearchCities` can return zero cities.** Check `len(search.JSON200.Cities) == 0` before indexing `[0]`.
194200
- **Person-pair and forecast bodies use anonymous nested structs** (`CalculateSynastry`, `CalculateGunMilan`, `GenerateTimeline` carry inline `Person1`/`Person2`/`BirthData` structs). They are awkward to build as a Go literal; for those, see https://roxyapi.com/api-reference for the JSON shape.
195201
- **`SearchCities` paginates** with `Limit` and `Offset` (`roxyapi.Ptr(20)`); the default page is 10.
196202
- **One direct runtime dependency.** `go get` pulls `github.com/oapi-codegen/runtime` (Apache 2.0); it brings two small transitive modules (`google/uuid`, `apapsch/go-jsonmerge`). The HTTP layer is the standard library `net/http`.

README.md

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,15 @@ func main() {
5454
}
5555
ctx := context.Background()
5656

57-
// Step 1: geocode the birth city (required for any chart endpoint).
58-
search, err := roxy.Location.SearchCities(ctx, &roxyapi.SearchCitiesParams{Q: "London, UK"})
57+
// Step 1: geocode the birth city (required for any chart endpoint). Use a full
58+
// country name, not an abbreviation ("London, United Kingdom", not "London, UK").
59+
search, err := roxy.Location.SearchCities(ctx, &roxyapi.SearchCitiesParams{Q: "London, United Kingdom"})
5960
if err != nil {
6061
panic(err)
6162
}
63+
if len(search.JSON200.Cities) == 0 {
64+
panic("no city matched the search") // a 200 can still return zero cities
65+
}
6266
city := search.JSON200.Cities[0] // City, Country, Latitude, Longitude, Timezone (IANA), UtcOffset
6367

6468
// Step 2: Western natal chart. Timezone is a union; pass the IANA string from the geocode.
@@ -181,6 +185,10 @@ yn, err := roxy.Tarot.CastYesNo(ctx, nil, roxyapi.CastYesNoJSONRequestBody{Quest
181185

182186
```go
183187
// Full bodygraph: type, strategy, authority, profile, centers, channels, gates.
188+
// Timezone union: for an inline-body endpoint the type name uses JSONBody, not
189+
// JSONRequestBody (see Gotchas). If unsure of the name, let autocomplete fill it.
190+
var btz roxyapi.GenerateBodygraphJSONBody_Timezone
191+
_ = btz.FromGenerateBodygraphJSONBodyTimezone1("America/New_York")
184192
hd, err := roxy.HumanDesign.GenerateBodygraph(ctx, nil, roxyapi.GenerateBodygraphJSONRequestBody{
185193
Date: roxyapi.Date(1990, time.July, 4), Time: "10:12:00",
186194
Latitude: roxyapi.Ptr[float32](40.7128), Longitude: roxyapi.Ptr[float32](-74.006), Timezone: btz,
@@ -248,6 +256,23 @@ Every endpoint is also a remote MCP tool at `https://roxyapi.com/mcp/{domain}` (
248256
- **Read responses off `JSON200`** (`resp.JSON200.Cities[0].Latitude`). It is nil unless the call was a 2xx (errors are returned, not in the body).
249257
- **Person-pair / forecast bodies use anonymous structs** (see note above) and are best built from the JSON shape in the API reference.
250258

259+
## FAQ
260+
261+
**Q: `SearchCities` returned 200 but `Cities[0]` panics, or my city is not found.**
262+
A: A successful search can still return an empty `Cities` slice, so check `len(search.JSON200.Cities) == 0` before indexing. Two-letter country abbreviations are not matched: use the full country name (`"London, United Kingdom"`, not `"London, UK"`) or just the bare city (`"London"`).
263+
264+
**Q: I got `nil pointer dereference` in `applyEditors`. What did I do?**
265+
A: You passed `nil` to an endpoint that has no query-parameters argument (`roxy.Usage.GetUsageStats`, `roxy.Languages.ListLanguages`, `roxy.Crystals.ListCrystalColors`, `roxy.Crystals.ListCrystalPlanets`, `roxy.Dreams.GetSymbolLetterCounts`). That `nil` is read as a request editor: the call compiles, then panics at runtime. Call them with `ctx` only, for example `roxy.Usage.GetUsageStats(ctx)`.
266+
267+
**Q: `NewRoxy` returned no error but every call is `401 api_key_required`.**
268+
A: Make sure `ROXYAPI_KEY` is exported. `NewRoxy` returns an error for an empty key; a non-empty but wrong key only fails on the first request.
269+
270+
**Q: How do I build the `Timezone` when I cannot guess the union type name?**
271+
A: The union type is `<Request>_Timezone` for a named request body (`NatalChartRequest_Timezone`) and `<Operation>JSONBody_Timezone` for an inline body (`GenerateBodygraphJSONBody_Timezone`). If you cannot guess it, write the `Timezone:` field with any value and read the expected type from the compiler error, or let autocomplete fill it. Build it with `.From<Type>Timezone1("IANA")` or `.From<Type>Timezone0(decimal)`.
272+
273+
**Q: What type does `NewRoxy` return, so I can store it or pass it to a function?**
274+
A: `*roxyapi.Roxy`.
275+
251276
## Error handling and advanced use
252277

253278
Any 4xx or 5xx is returned as a `*RoxyError`. Switch on `Code` (stable); `Message` is human readable.

client.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package roxyapi
22

33
import (
44
"context"
5+
"errors"
56
"net/http"
67
)
78

@@ -16,7 +17,13 @@ const DefaultBaseURL = "https://roxyapi.com/api/v2"
1617
// at a different host, WithHTTPClient to supply a custom *http.Client, or
1718
// WithRequestEditorFn to add a header. The API key and SDK identification headers are
1819
// always applied first.
20+
//
21+
// NewRoxy returns an error if apiKey is empty, so a missing ROXYAPI_KEY fails here
22+
// rather than as a confusing 401 on the first call.
1923
func NewRoxy(apiKey string, opts ...ClientOption) (*Roxy, error) {
24+
if apiKey == "" {
25+
return nil, errors.New("roxyapi: an API key is required (set ROXYAPI_KEY)")
26+
}
2027
all := append([]ClientOption{
2128
WithRequestEditorFn(apiKeyEditor(apiKey)),
2229
WithRequestEditorFn(sdkClientEditor),

0 commit comments

Comments
 (0)