Go SDK for the Ask4Me API — a self-hosted Human-in-the-Loop service.
Official server repo: github.com/easychen/ask4me
Official website: ask4me.ft07.com
Ask4Me turns an HTTP request into a human interaction:
- Your code calls
POST /v1/askwith a title, body, and optional buttons / form. - The server pushes a notification (ServerChan / Apprise) with an interaction link.
- The user opens the link, clicks a button or types a reply.
- Your request returns the result.
go get github.com/lib-x/ask4menpm install -g ask4me-server
ask4me-server --config ./.envMinimum .env:
ASK4ME_BASE_URL=https://your-domain.com
ASK4ME_API_KEY=change-me
ASK4ME_SERVERCHAN_SENDKEY=your-sendkey # or ASK4ME_APPRISE_URLS
ASK4ME_TERMINAL_CACHE_SECONDS=300func main() {
const (
endpoint = "http://localhost:8080/v1/ask"
apiKey = "change-me"
)
// Build the MCD using the fluent helper.
mcd := ask4me.NewMCD().
Buttons(
ask4me.Button{Label: "OK", Value: "ok"},
ask4me.Button{Label: "Later", Value: "later"},
).
Input("note", "补充说明", "提交").
Build()
result, err := ask4me.Ask(ask4me.AskOptions{
Endpoint: endpoint,
APIKey: apiKey,
// Stream: false is the default — uses nonStream mode.
Payload: &ask4me.Payload{
Title: "Ask4Me Demo",
Body: "这是一条来自 Ask4Me 的测试消息,请随便点一个按钮或回一段话。",
MCD: mcd,
ExpiresInSeconds: 600,
},
})
if err != nil {
log.Fatalf("ask failed: %v", err)
}
out, _ := json.MarshalIndent(json.RawMessage(result.Result.Raw), "", " ")
fmt.Println(string(out))
}The call blocks until the user submits or the request expires, then returns a single JSON response. No streaming complexity required.
result, err := ask4me.Ask(ask4me.AskOptions{
Endpoint: "http://localhost:8080/v1/ask",
APIKey: "change-me",
// Stream: false ← default
Payload: &ask4me.Payload{
Title: "Approve?",
Body: "Please confirm the production deployment.",
MCD: ask4me.NewMCD().Buttons(
ask4me.Button{Label: "Deploy", Value: "deploy"},
ask4me.Button{Label: "Abort", Value: "abort"},
).Build(),
ExpiresInSeconds: 600,
},
})
// result.Result is the terminal eventReceive events in real time. The request.created event includes
interaction_url — useful if you want to display or log the link directly
rather than waiting for the notification channel.
result, err := ask4me.Ask(ask4me.AskOptions{
Endpoint: endpoint,
APIKey: apiKey,
Stream: true,
Payload: &ask4me.Payload{ /* … */ },
OnEvent: func(ev *ask4me.Event) {
fmt.Printf("[%s] id=%s url=%s\n", ev.Type, ev.ID, ev.InteractionURL)
},
})mcd := ask4me.NewMCD().
Buttons(
ask4me.Button{Label: "OK", Value: "ok"},
ask4me.Button{Label: "Later", Value: "later"},
).
Input("note", "Leave a note", "Submit").
Build()Or write the string by hand:
:::buttons
- [OK](ok)
- [Later](later)
:::
:::input name="note" label="Leave a note" submit="Submit"
:::
JSONForms is POST-only. When set, it overrides MCD.
result, err := ask4me.Ask(ask4me.AskOptions{
Endpoint: endpoint,
APIKey: apiKey,
Payload: &ask4me.Payload{
Title: "User info",
Body: "Please fill in the form.",
JSONForms: &ask4me.JSONFormsConfig{
Schema: json.RawMessage(`{"type":"object","properties":{"name":{"type":"string"}},"required":["name"]}`),
SubmitLabel: "Submit",
},
ExpiresInSeconds: 600,
},
})
// form data is in result.Result.Data.Payload// First attempt — may time out before user responds.
result, err := ask4me.Ask(ask4me.AskOptions{
Endpoint: endpoint,
APIKey: apiKey,
RequestID: "req_myjob_001", // pre-generated; must start with "req_"
Payload: &ask4me.Payload{
RequestID: "req_myjob_001",
Title: "Confirm?",
/* … */
},
})
// Resume later — body is not re-sent when RequestID is provided.
result, err = ask4me.Ask(ask4me.AskOptions{
Endpoint: endpoint,
APIKey: apiKey,
RequestID: "req_myjob_001",
})When you cannot set HTTP headers, pass the API key as a ?key= query parameter.
Note that query params may appear in proxy/server logs.
result, err := ask4me.Ask(ask4me.AskOptions{
Endpoint: endpoint,
APIKey: apiKey,
AuthMode: ask4me.AuthQueryKey, // adds ?key=... to the URL
Payload: &ask4me.Payload{ /* … */ },
})client, err := ask4me.NewClient("http://localhost:8080/v1/ask", "change-me")
if err != nil { log.Fatal(err) }
result, err := client.Ask(ask4me.AskOptions{
Payload: &ask4me.Payload{ /* … */ },
})| Field | Type | Default | Description |
|---|---|---|---|
Endpoint |
string |
— | Required. Full API URL |
APIKey |
string |
— | Required. Bearer token |
AuthMode |
AuthMode |
AuthBearer |
AuthBearer or AuthQueryKey |
Payload |
*Payload |
nil | Typed request payload |
RawPayload |
any |
nil | Untyped payload (used when Payload is nil) |
Stream |
bool |
false | true = SSE mode, false = nonStream |
OnEvent |
func(*Event) |
nil | Per-event callback (SSE mode only) |
Context |
context.Context |
Background() |
Cancellation / timeout |
MaxRetries |
int |
0 (unlimited) | Max reconnect attempts |
InitialBackoff |
time.Duration |
200 ms | Starting retry sleep |
MaxBackoff |
time.Duration |
2 s | Retry sleep ceiling |
RequestID |
string |
— | Resume an existing request |
LastEventID |
string |
— | Resume SSE from a specific event |
HTTPClient |
*http.Client |
http.DefaultClient |
Custom HTTP client |
| Field | Type | Description |
|---|---|---|
Title |
string |
Notification title |
Body |
string |
Notification body |
MCD |
string |
Interaction control description (buttons / input) |
JSONForms |
*JSONFormsConfig |
Schema-driven form (POST only, overrides MCD) |
ExpiresInSeconds |
int |
TTL for the request |
RequestID |
string |
Pre-generated ID (must start with req_) |
ServerChanActionLinks |
bool |
Enable ServerChan 3 Action Links (requires https base URL) |
type AskResult struct {
RequestID string // server-assigned request ID
LastEventID string // last SSE event ID (for resuming)
Events []*Event // all SSE events in order (SSE mode)
Result *Event // terminal event
}type Event struct {
Type string // "request.created", "user.submitted", "request.expired", "notify.failed"
ID string // event ID
RequestID string // request ID
InteractionURL string // set on "request.created" (SSE only)
Data *SubmitData // set on terminal events
Raw json.RawMessage // full original JSON
}
func (e *Event) IsTerminal() bool // true for user.submitted / request.expired / notify.failedtype SubmitData struct {
Action string // button value clicked
Text string // text typed by user
Payload json.RawMessage // form data when JSONForms was used
}Type |
Meaning |
|---|---|
user.submitted |
User clicked a button or submitted the form |
request.expired |
Timed out without submission |
notify.failed |
Notification delivery failed |
| Directory | Description |
|---|---|
examples/nonstream |
Default mode — block until result |
examples/stream |
SSE mode — receive events in real time |
examples/jsonforms |
Schema-driven form |
examples/resume |
Pre-generated request_id + connection resume |
examples/querykey |
GET-style auth without Authorization header |
- Official server: github.com/easychen/ask4me
- Official JS SDK: ask4me/sdk-js
- Website & docs: ask4me.ft07.com