For developers

Put your Lina inside your own software.

The Lina API answers from the knowledge an organization has written down, in the shape OpenAI’s libraries already speak. Make a Lina API key in the Command Center, change one address in your software, and ask.

Quick start

Ask your first question

1. Make a Lina API key. Sign in, open Settings, then API access, and choose Make a key. It is shown once. Save it in your server’s secret storage as LINA_API_KEY.

2. Send a question. Every request goes to https://api.linasystems.org/v1 with the key in an Authorization: Bearer header.

curl https://api.linasystems.org/v1/chat/completions \
  -H "Authorization: Bearer $LINA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "lina", "messages": [{"role": "user", "content": "What are your opening hours?"}]}'

3. Or point OpenAI’s library at it. Change the base URL and use the model lina; the rest of what you wrote stays as it is.

Python
import os
from openai import OpenAI

client = OpenAI(base_url="https://api.linasystems.org/v1", api_key=os.environ["LINA_API_KEY"])
reply = client.chat.completions.create(
    model="lina",
    messages=[{"role": "user", "content": "What are your opening hours?"}],
)
print(reply.choices[0].message.content)

JavaScript
import OpenAI from "openai";

const client = new OpenAI({ baseURL: "https://api.linasystems.org/v1", apiKey: process.env.LINA_API_KEY });
const reply = await client.chat.completions.create({
  model: "lina",
  messages: [{ role: "user", content: "What are your opening hours?" }],
});
console.log(reply.choices[0].message.content);

What it does

Ask, read, add and be told

Every endpoint
MethodPathWhat it doesScope
POST/chat/completionsCreate a chat completionchat
POST/answersAsk one questionchat
GET/modelsList modelschat
GET/knowledgeList knowledge cardsNeeds the Knowledge module on the planknowledge:read
POST/knowledgeAdd a knowledge cardNeeds the Knowledge module on the planknowledge:write
DELETE/knowledge/{knowledge_id}Delete a knowledge cardNeeds the Knowledge module on the planknowledge:write
GET/callsList callsNeeds the Phone line on the plancalls:read
GET/calls/{call_id}Get a callNeeds the Phone line on the plancalls:read
POST/textsSend a textNeeds texting on the plantexts:send
GET/usageGet this month's usageusage:read
GET/webhooksList webhookswebhooks:manage
POST/webhooksAdd a webhookwebhooks:manage
DELETE/webhooks/{webhook_id}Remove a webhookwebhooks:manage

The same list, with every field, is the OpenAPI file.

Keys

A key does only what it was given

A key belongs to one organization and carries only the scopes it was given when it was made. Every plan includes API access. An organization can hold at most 25 keys at once. Keep a key on a server: never in a web page or an app. A key that is stopped is refused on the next call to the server that handled the stop, and within 30 seconds on every other.

The scopes
ScopeWhat it allows
chatAsk questions: chat completions, answers and the model list.
knowledge:readRead the organization's knowledge cards.
knowledge:writeAdd knowledge cards, and delete the ones the API added.
calls:readRead calls: who rang, when, the outcome and the summary.
calls:transcriptsAlso read what was said on a call. Kept apart because it is the caller's own speech.
texts:sendSend a text to a person who agreed to get one.
usage:readRead this month's usage, for the organization and for each key.
webhooks:manageAdd, list and remove webhooks.

Limits

What to count on

How answers arrive

Read this before you stream

Webhooks

A webhook is told that something happened, never what was said. The body names the event and the ids involved, so you fetch the rest with the API, within the scopes the key was given. Nothing is delivered until the organization switches delivery on, which is off when a space starts: GET /webhooks reports it as delivery_enabled, and Settings, then API access, has a Turn on delivery button. The events:

  • chat.handoff_requested
  • call.received
  • call.escalated
  • text.received
  • form.submitted
  • email.received
  • knowledge.updated
  • usage.allowance_reached

Each delivery carries X-Lina-Signature: t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of <t>.<body> with the webhook’s signing secret. Refuse a delivery whose t is more than 5 minutes old. Answer with any 2xx. A delivery is tried once, so fetch anything you missed from the API.

import hashlib
import hmac
import time


def verify(secret, header, body, tolerance=300):
    parts = dict(piece.split("=", 1) for piece in header.split(","))
    stamp = int(parts["t"])
    if abs(time.time() - stamp) > tolerance:
        return False
    message = f"{stamp}.".encode() + body
    expected = hmac.new(secret.encode(), message, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])

Errors

Every failure has the same shape. Branch on code, and quote request_id when you write to support.

{"error": {"message": "That API key is not valid. It may be mistyped, revoked or expired.",
           "type": "authentication_error", "code": "invalid_api_key", "param": null,
           "request_id": "0123456789abcdef"}}
CodeStatusWhat it means
missing_api_key401No API key was sent. Send it as 'Authorization: Bearer <key>'.
invalid_api_key401The key is not valid: mistyped, revoked or expired.
account_paused402The organization's billing is paused, so its API is paused too.
payment_required402The organization has not finished signing up.
missing_scope403The key was not given the scope this endpoint needs.
module_disabled403The organization's plan does not include what this endpoint belongs to.
secret_key_in_browser403A secret key was sent with an Origin header, which browsers add. Call the API from a server, and do not pass a browser's headers on to it.
knowledge_read_disabled403Reading knowledge from outside is switched off for this organization.
reserved_row403The card was written in the Command Center, so it can only be removed there.
one_to_one_only403Lina's own number texts one person at a time, so it cannot send through the API.
no_module403The organization's plan cannot send texts.
not_registered403The organization's texting is not approved by the carriers yet.
wrong_host404The request went to a host other than the API host.
forbidden403That is not allowed for this key.
unauthorized401Authentication failed.
invalid_json400The body is not valid JSON.
invalid_request_body400The body must be a JSON object.
invalid_request400, 422A parameter or the request as a whole could not be processed.
bad_request400The request could not be understood.
invalid_value400A value has the wrong type or is empty.
missing_required_parameter400A required parameter was not sent.
model_not_found400The model is not 'lina'. This API serves one model.
unsupported_parameter400The parameter is not supported: tools, functions, structured output and the like.
empty_array400A list that must hold at least one item is empty.
array_above_max_length400A list holds more items than the endpoint allows.
string_above_max_length400A text is longer than the endpoint allows.
invalid_cursor400The 'starting_after' value is not the id of an item in the list.
invalid_idempotency_key400The Idempotency-Key is not 1 to 64 letters, digits and . _ : -.
invalid_event400An event type does not exist.
invalid_url400The URL is not a public http(s) address.
request_too_large413The body is larger than the limit.
unsupported_media_type415Send the body as JSON.
not_found404There is no such endpoint, or no such card, call or webhook.
method_not_allowed405That method is not allowed on this endpoint.
idempotency_key_reused422The Idempotency-Key was already used for a different request.
idempotency_in_progress409A request with that Idempotency-Key is still being processed.
conflict409The request conflicts with the current state.
body_too_short422The card's body is too short to be a fact the assistants can use.
reserved_line422A line starts with DIRECTIVE:, ALWAYS:, NEVER: or PIN:, which only a person may write.
labeled_fact422A line names a fact the assistants keep, which only a person may set.
reserved_text422The text marks a note as private to the organization.
reserved_tag422A tag is reserved for the Command Center.
too_many_cards409The organization holds as many cards written through the API as it may.
too_many_cards_today409The organization wrote as many cards through the API today as it may.
too_many_webhooks409The organization has as many webhooks as it may.
bad_number422The number is not one a text can be sent to.
too_long422The text is too long.
no_consent422The person has not agreed to get texts from this organization.
opted_out422The person asked not to get texts.
quiet_hours409It is outside the hours texts may be sent. Try later.
voter_quiet_hours409It is outside the hours the recipient's area may be texted. Try later.
in_progress409A text with that Idempotency-Key is still being sent.
no_credits402The organization has no text credits left.
failed_unknown502The text may have been sent. Do not send it again.
twilio_unknown502The text may have been sent. Do not send it again.
text_failed502The text was not sent.
paused503Texting is paused for now. Try again later.
store_unavailable503Texting could not check its records just now. Try again.
twilio_error503The text carrier could not be reached just now. Try again.
rate_limit_exceeded429Too many requests this minute. Wait for Retry-After.
allowance_reached429The organization used all the answers its plan includes this month. See resets_at.
key_allowance_reached429The key used its own monthly cap of answers. See resets_at.
service_busy503Lina is at capacity for today. Try again later.
service_unavailable503Lina could not answer just now. Try again in a moment.
model_error502The assistant could not answer just now. Try again in a moment.
internal_error500Something went wrong on our side.
api_paused503The API is paused. Try again later.

Ready to build?

Make a key and ask

Sign in to your Command Center, open Settings, then API access. Use of the API follows the Terms of Service and the Acceptable Use Policy. Questions? Write to support@linasystems.org.