github.com/Diferentt/find-ai-sdk-go

Package findai is a Go client for the FindAI Studio "datasets" (knowledge module) API: list your dataset tables, inspect their structure, and create/read/update/delete rows, plus full-text search, semantic search, and CSV import.


License
Apache-2.0
Install
go get github.com/Diferentt/find-ai-sdk-go

Documentation

find-ai-sdk-go

CI Go Reference License

A Go client for FindAI Studio's datasets (a.k.a. the knowledge module): list your dataset tables, inspect their field schema, and create/read/update/delete rows — plus full-text search, semantic search, and CSV import.

This SDK is deliberately scoped to data operations. Creating or editing a dataset's schema (its "table structure") is a dashboard-only action; the SDK works with tables that already exist.

Install

go get github.com/Diferentt/find-ai-sdk-go

Requires Go 1.22+.

Quickstart

package main

import (
	"context"
	"fmt"
	"log"

	findai "github.com/Diferentt/find-ai-sdk-go"
)

func main() {
	client, err := findai.NewClient(
		"fai_your_api_key",
		findai.WithBaseURL("https://api.en-kel.com"),
	)
	if err != nil {
		log.Fatal(err)
	}

	ctx := context.Background()

	templates, err := client.ListTemplates(ctx)
	if err != nil {
		log.Fatal(err)
	}
	for _, t := range templates {
		fmt.Printf("%s: %s (%d fields)\n", t.ID, t.Name, len(t.Fields))
	}
}

More runnable examples: examples/basic_crud, examples/search, examples/csv_import.

Authentication

Requests are authenticated with a tenant-scoped API key (format fai_...) carrying the dataset:manage scope. Create one from your FindAI Studio dashboard's API keys section, then pass it to NewClient.

Note: https://api.en-kel.com is the API host — different from https://app.en-kel.com, which is the dashboard web app. WithBaseURL must point at the API host.

Usage

Templates (read-only)

A "template" is a dataset's schema — its name and field definitions. The SDK only reads templates; create/edit/delete them from the dashboard.

templates, err := client.ListTemplates(ctx)
tmpl, err := client.GetTemplate(ctx, "kt_abc123")
for _, f := range tmpl.Fields {
    fmt.Println(f.Name, f.Type, f.Required)
}

Records (CRUD)

A record is one row. Its values are arbitrary JSON shaped by the owning template's field schema, represented as map[string]any.

rec, err := client.CreateRecord(ctx, templateID, map[string]any{
    "company_name": "Acme Corp",
    "founded_year": 1999,
})

rec, err = client.GetRecord(ctx, templateID, rec.ID)
rec, err = client.UpdateRecord(ctx, templateID, rec.ID, map[string]any{"founded_year": 2000})
err = client.DeleteRecord(ctx, templateID, rec.ID)

Optional sugar for building/reading values without stringly-typed map literals:

values := findai.NewValuesBuilder().
    Set("company_name", "Acme Corp").
    Set("founded_year", 1999).
    Build()

name, _ := findai.AsString(rec.ValuesData, "company_name")
year, _ := findai.AsNumber(rec.ValuesData, "founded_year")

Pagination

ListRecords mirrors the API's offset/limit contract directly:

page, err := client.ListRecords(ctx, templateID, findai.ListRecordsOptions{Offset: 0, Limit: 50})
for _, r := range page.Records {
    // ...
}
if page.HasMore {
    // fetch the next page
}

For walking every record without manually tracking offsets, use the iterator:

it := client.ListRecordsIterator(ctx, templateID, findai.ListRecordsOptions{})
for it.Next() {
    rec := it.Record()
    // ...
}
if err := it.Err(); err != nil {
    log.Fatal(err)
}

Search

resp, err := client.Search(ctx, templateID, findai.SearchRequest{Query: "acme", Limit: 20})

results, err := client.SemanticSearch(ctx, templateID, findai.SemanticSearchRequest{
    Query: "enterprise software companies",
    TopK:  10,
})

CSV import

f, err := os.Open("companies.csv")
if err != nil {
    log.Fatal(err)
}
defer f.Close()

result, err := client.ImportCSV(ctx, templateID, "companies.csv", f)
fmt.Printf("imported %d/%d rows\n", result.Imported, result.TotalRows)
for _, rowErr := range result.Errors {
    fmt.Printf("row %d: %s\n", rowErr.Row, rowErr.Error)
}

Error handling

Every non-2xx response is returned as *findai.APIError:

_, err := client.GetRecord(ctx, templateID, "kr_missing")
if findai.IsNotFound(err) {
    // ...
}

var apiErr *findai.APIError
if errors.As(err, &apiErr) {
    fmt.Println(apiErr.StatusCode, apiErr.Detail)
    for _, fe := range apiErr.Errors { // populated for request-body validation errors
        fmt.Println(fe.Loc, fe.Msg)
    }
}

Helpers: IsNotFound, IsUnauthorized, IsForbidden, IsValidationError, IsRateLimited.

Configuration

client, err := findai.NewClient(apiKey,
    findai.WithBaseURL("https://api.en-kel.com"),                   // required
    findai.WithHTTPClient(customHTTPClient),                         // optional
    findai.WithTimeout(10 * time.Second),                            // optional
    findai.WithRetry(3, 200*time.Millisecond),                       // optional; retries 429/5xx/network errors
    findai.WithUserAgent("my-app/1.0"),                              // optional
)

Versioning

This module follows SemVer. It's currently pre-1.0 (v0.x) — the API may still shift release to release. See CHANGELOG.md.

Contributing

See CONTRIBUTING.md.

License

Apache-2.0