---
title: C SDK
description: "C SDK: ask the ten ThinkThen questions from C, and get yes, no or not sure back as a return code and a plain C answer."
goal: The reader runs a first decide in C, compiles against the library, handles not sure and failures through the return code, and finds the C call for each of the ten functions.
reader: A C developer who wants to ask questions about text from a program
kind: reference
verified: 2026-11-03
sidebar:
  label: "C"
---

# C SDK

The C SDK gives your program the ten functions as plain C calls; each call returns a code that says whether there is an answer, and writes the answer into your own variable, such as a `bool` from decide.

```c
#include <thinkthen.h>
#include <stdio.h>
#include <stdlib.h>

static void report(const thinkthen_error *error) {
    fprintf(stderr, "%s: ", thinkthen_code_name(error->code));
    fwrite(error->message.data, 1, error->message.len, stderr);
    fputc('\n', stderr);
}

int main(void) {
    thinkthen_client *client = NULL;
    thinkthen_error error = THINKTHEN_ERROR_INIT;
    bool is_refund = false;
    int rc = thinkthen_client_open(NULL, &client, &error);
    if (rc == THINKTHEN_OK)
        rc = thinkthen_decide(client, "Does the customer request a refund?",
                              "Please refund my order.",
                              THINKTHEN_NUL_TERMINATED, &is_refund, &error);
    if (rc == THINKTHEN_OK)
        puts(is_refund ? "true" : "false");
    else
        report(&error);
    thinkthen_error_free(&error);
    thinkthen_client_free(client);
    return rc == THINKTHEN_OK ? EXIT_SUCCESS : EXIT_FAILURE;
}
```

Output

```text
true
```


`true` means yes: the message asks for a refund.

It needs A C11 compiler and pkg-config. Extract the C archive for your platform from the [0.2.0 release](https://github.com/botassembly/thinkthen/releases/tag/v0.2.0) into `thinkthen`, save the program as `example.c`, then compile it and run `./example`:

```sh
cc -std=c11 -Wall -Wextra -Werror example.c -o example \
  $(PKG_CONFIG_PATH="$PWD/thinkthen/lib/pkgconfig" \
    pkg-config --cflags --libs thinkthen-c) \
  -Wl,-rpath,"$PWD/thinkthen/lib"
```

The `-Wl,-rpath` flag tells the program where to find the library when it runs. The client uses the key from the [Quickstart](/quickstart/).

## Read not sure and failures from the return code

Every call returns `THINKTHEN_OK` when it wrote an answer. It returns `THINKTHEN_UNSURE` when the model is not sure, and then it writes no answer, so not sure can never read as `false`. When ThinkThen can't judge the text, it returns a negative number, and `error` holds the code and its message.

To get not sure from decide, build the question and set a low bar and a high bar with `thinkthen_question_set_band`. `thinkthen_run_one` then asks it and fills in the result.

```c
#include <thinkthen.h>
#include <stdio.h>
#include <stdlib.h>

static void report(const thinkthen_error *error) {
    fprintf(stderr, "%s: ", thinkthen_code_name(error->code));
    fwrite(error->message.data, 1, error->message.len, stderr);
    fputc('\n', stderr);
}

int main(void) {
    thinkthen_client *client = NULL;
    thinkthen_question *refund = NULL;
    thinkthen_row result = THINKTHEN_ROW_INIT;
    thinkthen_error error = THINKTHEN_ERROR_INIT;
    int rc = thinkthen_client_open(NULL, &client, &error);
    if (rc == THINKTHEN_OK)
        rc = thinkthen_question_new(THINKTHEN_FUNCTION_DECIDE,
                                    "Does the customer request a refund?",
                                    THINKTHEN_NUL_TERMINATED, &refund, &error);
    // Below 0.2 is no, 0.8 and above is yes, and between is not sure.
    if (rc == THINKTHEN_OK)
        rc = thinkthen_question_set_band(refund, 0.2, 0.8, &error);
    if (rc == THINKTHEN_OK)
        rc = thinkthen_run_one(client, refund,
                               "I was charged twice for one invoice.",
                               THINKTHEN_NUL_TERMINATED, THINKTHEN_RECORD_TEXT,
                               &result, &error);
    if (rc == THINKTHEN_OK)
        puts(result.answer_bool ? "true" : "false");
    else if (rc == THINKTHEN_UNSURE)
        printf("not sure (%g): ask a person\n", result.probability);
    else
        report(&error);
    thinkthen_row_free(&result);
    thinkthen_question_free(refund);
    thinkthen_error_free(&error);
    thinkthen_client_free(client);
    return rc < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}
```

Output

```text
not sure (0.7): ask a person
```


A double charge might be a refund request, or might not, so decide is not sure. Send it to a person. `probability` is the chance of yes.

- The negative number is minus the error code, such as `-THINKTHEN_CODE_RECORD_EMPTY` for blank text, which is never sent. Compare with `THINKTHEN_OK`, never with `-1`.
- `thinkthen_code_name` gives the code's name, such as `record.empty`. `error.message` is counted bytes, so print it with its length. [Troubleshooting](/reference/troubleshooting/) lists each code.
- decide, filter and choose take a low and a high bar. choose and find can also be not sure without them. tag, score, rank, recognize and relate answer or fail. Each annotate answer follows its own question.
- With GCC and Clang, ignoring a return code draws a compiler warning.

Start every `thinkthen_error` with `THINKTHEN_ERROR_INIT` and every result with `THINKTHEN_ROW_INIT`. Passing one to another call frees what it held first. Free each with `thinkthen_error_free` or `thinkthen_row_free` after its last use. Clients, questions, calls and values each have their own free function, and every free function accepts `NULL`.

## The ten functions

Each program is a whole file that compiles with the line above, and asks the support-inbox question its function page asks.

### decide

<details class="example-disclosure">
<summary>Yes or no about one message</summary>

```c
#include <thinkthen.h>
#include <stdio.h>
#include <stdlib.h>

static void report(const thinkthen_error *error) {
    fprintf(stderr, "%s: ", thinkthen_code_name(error->code));
    fwrite(error->message.data, 1, error->message.len, stderr);
    fputc('\n', stderr);
}

int main(void) {
    thinkthen_client *client = NULL;
    thinkthen_error error = THINKTHEN_ERROR_INIT;
    bool is_refund = false;
    int rc = thinkthen_client_open(NULL, &client, &error);
    if (rc == THINKTHEN_OK)
        rc = thinkthen_decide(client, "Does the customer request a refund?",
                              "Please refund my order.",
                              THINKTHEN_NUL_TERMINATED, &is_refund, &error);
    if (rc == THINKTHEN_OK)
        puts(is_refund ? "true" : "false");
    else
        report(&error);
    thinkthen_error_free(&error);
    thinkthen_client_free(client);
    return rc == THINKTHEN_OK ? EXIT_SUCCESS : EXIT_FAILURE;
}
```

Output

```text
true
```


Yes: the message asks for a refund. `thinkthen_decide` writes the answer into your `bool`, and uses one bar at 0.5. For a low and a high bar, or to say what yes and no mean with `thinkthen_question_set_yes_means` and `thinkthen_question_set_no_means`, build a question and use `thinkthen_run_one`, as in the not sure example above. [decide](/functions/decide/) shows every option.

</details>

### choose

<details class="example-disclosure">
<summary>The one choice that fits</summary>

```c
#include <thinkthen.h>
#include <stdio.h>
#include <stdlib.h>

static void report(const thinkthen_error *error) {
    fprintf(stderr, "%s: ", thinkthen_code_name(error->code));
    fwrite(error->message.data, 1, error->message.len, stderr);
    fputc('\n', stderr);
}

int main(void) {
    const char *const topics[] = {"billing", "shipping", "other"};
    thinkthen_client *client = NULL;
    thinkthen_error error = THINKTHEN_ERROR_INIT;
    size_t topic = 0;
    int rc = thinkthen_client_open(NULL, &client, &error);
    // topic is the index of the chosen string in topics.
    if (rc == THINKTHEN_OK)
        rc = thinkthen_choose(client, "What is the message about?", topics,
                              3, "Please refund my order.",
                              THINKTHEN_NUL_TERMINATED, &topic, &error);
    if (rc == THINKTHEN_OK)
        puts(topics[topic]);
    else if (rc == THINKTHEN_UNSURE)
        puts("not sure");
    else
        report(&error);
    thinkthen_error_free(&error);
    thinkthen_client_free(client);
    return rc < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}
```

Output

```text
billing
```


A refund is about billing. `thinkthen_choose` writes the index of the chosen string in your choices, so your own string names the answer. It returns `THINKTHEN_UNSURE` when no choice reaches the bar. [choose](/functions/choose/) shows every option.

</details>

### filter

<details class="example-disclosure">
<summary>Only the messages that answer yes</summary>

```c
#include <thinkthen.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>

static void report(const thinkthen_error *error) {
    fprintf(stderr, "%s: ", thinkthen_code_name(error->code));
    fwrite(error->message.data, 1, error->message.len, stderr);
    fputc('\n', stderr);
}

static const char *const messages[] = {
    "Please refund my order.",
    "Where is my parcel?",
    "I was charged twice for one invoice.",
};

int main(void) {
    thinkthen_record_input records[3];
    for (size_t i = 0; i < 3; i++) {
        records[i].struct_size = sizeof records[i];
        records[i].kind = THINKTHEN_RECORD_TEXT;
        records[i].data = messages[i];
        records[i].len = THINKTHEN_NUL_TERMINATED;
    }
    thinkthen_client *client = NULL;
    thinkthen_question *asks_refund = NULL;
    thinkthen_call *call = NULL;
    thinkthen_row refund_request = THINKTHEN_ROW_INIT;
    thinkthen_error error = THINKTHEN_ERROR_INIT;
    int rc = thinkthen_client_open(NULL, &client, &error);
    if (rc == THINKTHEN_OK)
        rc = thinkthen_question_new(THINKTHEN_FUNCTION_FILTER,
                                    "Does the customer request a refund?",
                                    THINKTHEN_NUL_TERMINATED, &asks_refund,
                                    &error);
    if (rc == THINKTHEN_OK)
        rc = thinkthen_call_open_records_question(
            client, asks_refund, NULL, records, 3, sizeof records[0],
            THINKTHEN_OFFSET_UTF8, &call, &error);
    // Only the messages that answer yes. A failed message returns its
    // negative code in its place, and the rest carry on.
    while (rc == THINKTHEN_OK
           || refund_request.status == THINKTHEN_STATUS_FAILED) {
        rc = thinkthen_call_next_kept(call, &refund_request, &error);
        if (rc == THINKTHEN_OK) {
            puts(messages[refund_request.position - 1]);
        } else if (refund_request.status == THINKTHEN_STATUS_FAILED) {
            fprintf(stderr, "message %" PRIu64 " failed: ",
                    refund_request.position);
            report(&error);
        }
    }
    if (rc != THINKTHEN_END)
        report(&error);
    thinkthen_row_free(&refund_request);
    thinkthen_error_free(&error);
    thinkthen_call_free(call);
    thinkthen_question_free(asks_refund);
    thinkthen_client_free(client);
    return rc == THINKTHEN_END ? EXIT_SUCCESS : EXIT_FAILURE;
}
```

Output

```text
Please refund my order.
I was charged twice for one invoice.
```


The parcel question is left out. A filter asks one question of many messages through a call. `thinkthen_call_next_kept` gives only the messages that answer yes, and returns `THINKTHEN_END` after the last. A message that fails returns its own negative code in its place, and the rest carry on. `position` is the message's place in your input, starting at 1, and `thinkthen_row_original` gives back the message itself. [filter](/functions/filter/) shows every option.

</details>

### tag

<details class="example-disclosure">
<summary>Every label that fits</summary>

```c
#include <thinkthen.h>
#include <stdio.h>
#include <stdlib.h>

static void report(const thinkthen_error *error) {
    fprintf(stderr, "%s: ", thinkthen_code_name(error->code));
    fwrite(error->message.data, 1, error->message.len, stderr);
    fputc('\n', stderr);
}

int main(void) {
    const char *const labels[] = {"billing", "shipping"};
    thinkthen_client *client = NULL;
    thinkthen_error error = THINKTHEN_ERROR_INIT;
    bool appears[2] = {false, false};
    int rc = thinkthen_client_open(NULL, &client, &error);
    // appears gets one answer per label, in label order.
    if (rc == THINKTHEN_OK)
        rc = thinkthen_tag(client, "Which topics appear?", labels, 2,
                           "I was charged twice and my parcel is late again.",
                           THINKTHEN_NUL_TERMINATED, appears, &error);
    if (rc == THINKTHEN_OK) {
        // Every label that fits, possibly none.
        for (size_t i = 0; i < 2; i++)
            if (appears[i])
                puts(labels[i]);
    } else {
        report(&error);
    }
    thinkthen_error_free(&error);
    thinkthen_client_free(client);
    return rc == THINKTHEN_OK ? EXIT_SUCCESS : EXIT_FAILURE;
}
```

Output

```text
billing
shipping
```


The message is about both billing and shipping. `thinkthen_tag` writes one `bool` per label, in label order, and all of them may be `false`. [tag](/functions/tag/) shows every option.

</details>

### score

<details class="example-disclosure">
<summary>A number from 0 to 1 on your scale</summary>

```c
#include <thinkthen.h>
#include <stdio.h>
#include <stdlib.h>

static void report(const thinkthen_error *error) {
    fprintf(stderr, "%s: ", thinkthen_code_name(error->code));
    fwrite(error->message.data, 1, error->message.len, stderr);
    fputc('\n', stderr);
}

int main(void) {
    const char *const levels[] = {"routine", "urgent"};
    thinkthen_client *client = NULL;
    thinkthen_error error = THINKTHEN_ERROR_INIT;
    double urgency = 0;
    int rc = thinkthen_client_open(NULL, &client, &error);
    // 0 means routine and 1 means urgent.
    if (rc == THINKTHEN_OK)
        rc = thinkthen_score(client, "How urgent is the request?", levels, 2,
                             "Please refund my order.",
                             THINKTHEN_NUL_TERMINATED, &urgency, &error);
    if (rc == THINKTHEN_OK)
        printf("%g\n", urgency);
    else
        report(&error);
    thinkthen_error_free(&error);
    thinkthen_client_free(client);
    return rc == THINKTHEN_OK ? EXIT_SUCCESS : EXIT_FAILURE;
}
```

Output

```text
0.05
```


The levels run lowest first, so a plain refund request scores near routine. `thinkthen_score` writes a `double` from 0 for the first level to 1 for the last. [score](/functions/score/) shows every option.

</details>

### rank

<details class="example-disclosure">
<summary>Every message, highest first</summary>

```c
#include <thinkthen.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>

static void report(const thinkthen_error *error) {
    fprintf(stderr, "%s: ", thinkthen_code_name(error->code));
    fwrite(error->message.data, 1, error->message.len, stderr);
    fputc('\n', stderr);
}

static const char *const messages[] = {
    "Please refund my order.",
    "Where is my parcel?",
    "I was charged twice for one invoice.",
};

int main(void) {
    thinkthen_record_input records[3];
    for (size_t i = 0; i < 3; i++) {
        records[i].struct_size = sizeof records[i];
        records[i].kind = THINKTHEN_RECORD_TEXT;
        records[i].data = messages[i];
        records[i].len = THINKTHEN_NUL_TERMINATED;
    }
    thinkthen_client *client = NULL;
    thinkthen_question *by_urgency = NULL;
    thinkthen_call *call = NULL;
    thinkthen_row ranked = THINKTHEN_ROW_INIT;
    thinkthen_error error = THINKTHEN_ERROR_INIT;
    int rc = thinkthen_client_open(NULL, &client, &error);
    if (rc == THINKTHEN_OK)
        rc = thinkthen_question_new(THINKTHEN_FUNCTION_RANK,
                                    "How urgent is the request?",
                                    THINKTHEN_NUL_TERMINATED, &by_urgency,
                                    &error);
    if (rc == THINKTHEN_OK)
        rc = thinkthen_question_add_level(by_urgency, "routine",
                                          THINKTHEN_NUL_TERMINATED, &error);
    if (rc == THINKTHEN_OK)
        rc = thinkthen_question_add_level(by_urgency, "urgent",
                                          THINKTHEN_NUL_TERMINATED, &error);
    if (rc == THINKTHEN_OK)
        rc = thinkthen_call_open_records_question(
            client, by_urgency, NULL, records, 3, sizeof records[0],
            THINKTHEN_OFFSET_UTF8, &call, &error);
    // Rank 1 is the most urgent. position is the message's place in
    // messages, starting at 1. A failed message is a row of its own.
    while (rc == THINKTHEN_OK
           && (rc = thinkthen_call_next(call, &ranked, &error))
                  == THINKTHEN_OK) {
        if (ranked.status == THINKTHEN_STATUS_ANSWERED)
            printf("%" PRIu64 " %s\n", ranked.rank,
                   messages[ranked.position - 1]);
        else
            fprintf(stderr, "message %" PRIu64 " failed: %s\n",
                    ranked.position, thinkthen_code_name(ranked.error_code));
    }
    if (rc != THINKTHEN_END)
        report(&error);
    thinkthen_row_free(&ranked);
    thinkthen_error_free(&error);
    thinkthen_call_free(call);
    thinkthen_question_free(by_urgency);
    thinkthen_client_free(client);
    return rc == THINKTHEN_END ? EXIT_SUCCESS : EXIT_FAILURE;
}
```

Output

```text
1 I was charged twice for one invoice.
2 Where is my parcel?
3 Please refund my order.
```


The double charge is the most urgent. Rank compares many messages, so it runs through a call. Each result's `rank` is its answer, 1 for the most urgent, and its `position` points back to your message. `thinkthen_question_set_top` keeps only the first few. [rank](/functions/rank/) shows every option.

</details>

### find

<details class="example-disclosure">
<summary>The line that answers the question</summary>

```c
#include <thinkthen.h>
#include <stdio.h>
#include <stdlib.h>

static void report(const thinkthen_error *error) {
    fprintf(stderr, "%s: ", thinkthen_code_name(error->code));
    fwrite(error->message.data, 1, error->message.len, stderr);
    fputc('\n', stderr);
}

int main(void) {
    const char *message =
        "Morgan works for Example Group.\nPlease refund my order.";
    thinkthen_client *client = NULL;
    thinkthen_error error = THINKTHEN_ERROR_INIT;
    thinkthen_span action = {.present = 0};
    int rc = thinkthen_client_open(NULL, &client, &error);
    // action points into message, so keep message while you read it.
    if (rc == THINKTHEN_OK)
        rc = thinkthen_find(client, "Which line states the requested action?",
                            message, THINKTHEN_NUL_TERMINATED, &action,
                            &error);
    if (rc == THINKTHEN_OK && action.present) {
        fwrite(action.text.data, 1, action.text.len, stdout);
        putchar('\n');
    } else if (rc == THINKTHEN_OK) {
        puts("no line answers");
    } else if (rc == THINKTHEN_UNSURE) {
        puts("not sure");
    } else {
        report(&error);
    }
    thinkthen_error_free(&error);
    thinkthen_client_free(client);
    return rc < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}
```

Output

```text
Please refund my order.
```


The second line states the action. `thinkthen_find` writes a span whose `present` is 0 when no line answers. Its `start` and `end` are byte offsets, and its `text` points into your own text, so keep that text while you read the span. [find](/functions/find/) shows every option.

</details>

### recognize

<details class="example-disclosure">
<summary>Each name in the text and its kind</summary>

```c
#include <thinkthen.h>
#include <stdio.h>
#include <stdlib.h>

static void report(const thinkthen_error *error) {
    fprintf(stderr, "%s: ", thinkthen_code_name(error->code));
    fwrite(error->message.data, 1, error->message.len, stderr);
    fputc('\n', stderr);
}

int main(void) {
    const char *const kinds[] = {"person", "organization"};
    thinkthen_client *client = NULL;
    thinkthen_error error = THINKTHEN_ERROR_INIT;
    thinkthen_value *names = NULL;
    size_t count = 0;
    // recognize takes kinds, not question wording.
    int rc = thinkthen_client_open(NULL, &client, &error);
    if (rc == THINKTHEN_OK)
        rc = thinkthen_recognize(client, kinds, 2,
                                 "Morgan works for Example Group.",
                                 THINKTHEN_NUL_TERMINATED, &names, &error);
    if (rc == THINKTHEN_OK)
        rc = thinkthen_value_name_count(names, &count, &error);
    for (size_t i = 0; rc == THINKTHEN_OK && i < count; i++) {
        thinkthen_name name;
        rc = thinkthen_value_name_at(names, i, &name, &error);
        if (rc == THINKTHEN_OK) {
            fwrite(name.kind.data, 1, name.kind.len, stdout);
            fputs(": ", stdout);
            fwrite(name.text.data, 1, name.text.len, stdout);
            putchar('\n');
        }
    }
    if (rc != THINKTHEN_OK)
        report(&error);
    thinkthen_value_free(names);
    thinkthen_error_free(&error);
    thinkthen_client_free(client);
    return rc == THINKTHEN_OK ? EXIT_SUCCESS : EXIT_FAILURE;
}
```

Output

```text
person: Morgan
organization: Example Group
```


`thinkthen_recognize` writes a new value. Read its names with `thinkthen_value_name_count` and `thinkthen_value_name_at`, before you free it with `thinkthen_value_free`. [recognize](/functions/recognize/) shows every option.

</details>

### relate

<details class="example-disclosure">
<summary>Names linked by the relations you allow</summary>

```c
#include <thinkthen.h>
#include <stdio.h>
#include <stdlib.h>

static void report(const thinkthen_error *error) {
    fprintf(stderr, "%s: ", thinkthen_code_name(error->code));
    fwrite(error->message.data, 1, error->message.len, stderr);
    fputc('\n', stderr);
}

static void print_text(thinkthen_text text) {
    fwrite(text.data, 1, text.len, stdout);
}

int main(void) {
    const char *message =
        "Morgan works for Example Group.\nPlease refund my order.";
    const thinkthen_rule works_for = {
        .relation = {"works_for", THINKTHEN_NUL_TERMINATED},
        .source = {"person", THINKTHEN_NUL_TERMINATED},
        .target = {"organization", THINKTHEN_NUL_TERMINATED},
    };
    thinkthen_client *client = NULL;
    thinkthen_error error = THINKTHEN_ERROR_INIT;
    thinkthen_value *relations = NULL;
    size_t count = 0;
    // relate takes rules, not question wording.
    int rc = thinkthen_client_open(NULL, &client, &error);
    if (rc == THINKTHEN_OK)
        rc = thinkthen_relate(client, &works_for, 1, message,
                              THINKTHEN_NUL_TERMINATED, &relations, &error);
    if (rc == THINKTHEN_OK)
        rc = thinkthen_value_relation_count(relations, &count, &error);
    for (size_t i = 0; rc == THINKTHEN_OK && i < count; i++) {
        thinkthen_relation relation;
        rc = thinkthen_value_relation_at(relations, i, &relation, &error);
        if (rc == THINKTHEN_OK) {
            print_text(relation.source.text);
            fputs(" --", stdout);
            print_text(relation.relation);
            fputs("--> ", stdout);
            print_text(relation.target.text);
            putchar('\n');
        }
    }
    if (rc != THINKTHEN_OK)
        report(&error);
    thinkthen_value_free(relations);
    thinkthen_error_free(&error);
    thinkthen_client_free(client);
    return rc == THINKTHEN_OK ? EXIT_SUCCESS : EXIT_FAILURE;
}
```

Output

```text
Morgan --works_for--> Example Group
```


A `thinkthen_rule` names the kind of name at each end and the relation between them. `thinkthen_relate` takes an array of rules and writes a new value; read it with `thinkthen_value_relation_count` and `thinkthen_value_relation_at`. [relate](/functions/relate/) shows every option.

</details>

### annotate

<details class="example-disclosure">
<summary>Several questions for each message</summary>

```c
#include <thinkthen.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>

static void report(const thinkthen_error *error) {
    fprintf(stderr, "%s: ", thinkthen_code_name(error->code));
    fwrite(error->message.data, 1, error->message.len, stderr);
    fputc('\n', stderr);
}

// Print the answer named name, "not sure", or "failed" with its code.
static void print_answer(const thinkthen_value *form, const char *name) {
    thinkthen_error error = THINKTHEN_ERROR_INIT;
    thinkthen_value *answer = NULL;
    thinkthen_function function = THINKTHEN_FUNCTION_DECIDE;
    uint8_t is_yes = 0;
    thinkthen_text label = {.len = 0};
    double number = 0;
    int rc = thinkthen_value_annotation_get(form, name,
                                            THINKTHEN_NUL_TERMINATED, &answer,
                                            &error);
    if (rc == THINKTHEN_OK)
        rc = thinkthen_value_function(answer, &function, &error);
    if (rc == THINKTHEN_OK && function == THINKTHEN_FUNCTION_DECIDE)
        rc = thinkthen_value_bool(answer, &is_yes, &error);
    else if (rc == THINKTHEN_OK && function == THINKTHEN_FUNCTION_CHOOSE)
        rc = thinkthen_value_choice(answer, &label, &error);
    else if (rc == THINKTHEN_OK)
        rc = thinkthen_value_number(answer, &number, &error);
    if (rc == THINKTHEN_UNSURE)
        fputs("not sure", stdout);
    else if (rc != THINKTHEN_OK)
        printf("failed (%s)", thinkthen_code_name(error.code));
    else if (function == THINKTHEN_FUNCTION_DECIDE)
        fputs(is_yes ? "true" : "false", stdout);
    else if (function == THINKTHEN_FUNCTION_CHOOSE)
        fwrite(label.data, 1, label.len, stdout);
    else
        printf("%g", number);
    thinkthen_value_free(answer);
    thinkthen_error_free(&error);
}

static const char *const messages[] = {
    "Please refund my order.",
    "Where is my parcel?",
    "I was charged twice for one invoice.",
};

int main(void) {
    thinkthen_record_input records[3];
    for (size_t i = 0; i < 3; i++) {
        records[i].struct_size = sizeof records[i];
        records[i].kind = THINKTHEN_RECORD_TEXT;
        records[i].data = messages[i];
        records[i].len = THINKTHEN_NUL_TERMINATED;
    }
    const char *const names[] = {"refund", "topic", "urgency"};
    const char *const choices[] = {"billing", "shipping", "other"};
    const char *const levels[] = {"routine", "urgent"};
    thinkthen_client *client = NULL;
    thinkthen_question *refund = NULL, *topic = NULL, *urgency = NULL;
    thinkthen_question *triage = NULL;
    thinkthen_call *call = NULL;
    thinkthen_row annotated = THINKTHEN_ROW_INIT;
    thinkthen_value *form = NULL;
    thinkthen_error error = THINKTHEN_ERROR_INIT;
    int rc = thinkthen_client_open(NULL, &client, &error);
    // The form: three named questions, each asked of every message.
    if (rc == THINKTHEN_OK)
        rc = thinkthen_question_new(THINKTHEN_FUNCTION_DECIDE,
                                    "Does the customer request a refund?",
                                    THINKTHEN_NUL_TERMINATED, &refund, &error);
    if (rc == THINKTHEN_OK)
        rc = thinkthen_question_new(THINKTHEN_FUNCTION_CHOOSE,
                                    "What is the message about?",
                                    THINKTHEN_NUL_TERMINATED, &topic, &error);
    for (size_t i = 0; rc == THINKTHEN_OK && i < 3; i++)
        rc = thinkthen_question_add_choice(topic, choices[i],
                                           THINKTHEN_NUL_TERMINATED, &error);
    if (rc == THINKTHEN_OK)
        rc = thinkthen_question_new(THINKTHEN_FUNCTION_SCORE,
                                    "How urgent is the request?",
                                    THINKTHEN_NUL_TERMINATED, &urgency,
                                    &error);
    for (size_t i = 0; rc == THINKTHEN_OK && i < 2; i++)
        rc = thinkthen_question_add_level(urgency, levels[i],
                                          THINKTHEN_NUL_TERMINATED, &error);
    // annotate takes named questions, not question wording.
    if (rc == THINKTHEN_OK)
        rc = thinkthen_question_new(THINKTHEN_FUNCTION_ANNOTATE, "", 0,
                                    &triage, &error);
    const thinkthen_question *const parts[] = {refund, topic, urgency};
    for (size_t n = 0; rc == THINKTHEN_OK && n < 3; n++)
        rc = thinkthen_question_add_entry(triage, names[n],
                                          THINKTHEN_NUL_TERMINATED, parts[n],
                                          &error);
    if (rc == THINKTHEN_OK)
        rc = thinkthen_call_open_records_question(
            client, triage, NULL, records, 3, sizeof records[0],
            THINKTHEN_OFFSET_UTF8, &call, &error);
    // One line per message: refund, topic and urgency. A failed message
    // is a row of its own, and the rest carry on.
    while (rc == THINKTHEN_OK
           && (rc = thinkthen_call_next(call, &annotated, &error))
                  == THINKTHEN_OK) {
        if (annotated.status == THINKTHEN_STATUS_FAILED) {
            printf("message %" PRIu64 " failed (%s)\n", annotated.position,
                   thinkthen_code_name(annotated.error_code));
            continue;
        }
        thinkthen_value_free(form);
        rc = thinkthen_row_values(&annotated, &form, &error);
        if (rc != THINKTHEN_OK)
            break;
        for (size_t n = 0; n < 3; n++) {
            if (n > 0)
                putchar(' ');
            print_answer(form, names[n]);
        }
        putchar('\n');
    }
    if (rc != THINKTHEN_END)
        report(&error);
    thinkthen_value_free(form);
    thinkthen_row_free(&annotated);
    thinkthen_call_free(call);
    thinkthen_question_free(triage);
    thinkthen_question_free(refund);
    thinkthen_question_free(topic);
    thinkthen_question_free(urgency);
    thinkthen_error_free(&error);
    thinkthen_client_free(client);
    return rc == THINKTHEN_END ? EXIT_SUCCESS : EXIT_FAILURE;
}
```

Output

```text
true billing 0.05
false shipping 0.14
true billing 0.45
```


An annotate question holds several named questions, added with `thinkthen_question_add_entry`. Over many messages it runs through a call; `thinkthen_annotate` asks it of one message. `thinkthen_row_values` gives a message's answers, and `thinkthen_value_annotation_get` reads one by name. Each answer returns `THINKTHEN_UNSURE` or its own failure code instead of writing an answer that isn't there. [annotate](/functions/annotate/) shows every option.

</details>

## The full API

For a JSON message, pass `THINKTHEN_RECORD_JSON`, and point the question at one field with `thinkthen_question_set_field`, such as `"/text"`. `thinkthen_call_open_question` takes messages one at a time, for input you don't hold up front. One thread drives a call, `thinkthen_call_cancel` stops it from any thread, and threads may share one client. The [C header](https://github.com/botassembly/thinkthen/blob/v0.2.0/crates/thinkthen-ffi/include/thinkthen.h) lists every type and function.
