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
- Ask. Chat completions in OpenAI’s format, or one question in and one answer out. The answer comes from the organization’s own knowledge, under the same rules as its Website chat.
- Read and add knowledge. List the cards the assistants answer from, add a card, and delete a card the API added.
- Read calls. Who rang, when, how it ended and a summary. What was said is included only for a key that is allowed to read it.
- Send texts. On the Campaign edition, on Pro or Complete, the plans that can text. To people who agreed to get them, through the same checks as the Command Center. There is no way to record that agreement through the API.
- See usage. This month’s answers, and what each key answered.
- Be told. Webhooks that name what happened and its ids, signed so you can check them.
Every endpoint
| Method | Path | What it does | Scope |
|---|---|---|---|
| POST | /chat/completions | Create a chat completion | chat |
| POST | /answers | Ask one question | chat |
| GET | /models | List models | chat |
| GET | /knowledge | List knowledge cardsNeeds the Knowledge module on the plan | knowledge:read |
| POST | /knowledge | Add a knowledge cardNeeds the Knowledge module on the plan | knowledge:write |
| DELETE | /knowledge/{knowledge_id} | Delete a knowledge cardNeeds the Knowledge module on the plan | knowledge:write |
| GET | /calls | List callsNeeds the Phone line on the plan | calls:read |
| GET | /calls/{call_id} | Get a callNeeds the Phone line on the plan | calls:read |
| POST | /texts | Send a textNeeds texting on the plan | texts:send |
| GET | /usage | Get this month's usage | usage:read |
| GET | /webhooks | List webhooks | webhooks:manage |
| POST | /webhooks | Add a webhook | webhooks:manage |
| DELETE | /webhooks/{webhook_id} | Remove a webhook | webhooks: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
| Scope | What it allows |
|---|---|
| chat | Ask questions: chat completions, answers and the model list. |
| knowledge:read | Read the organization's knowledge cards. |
| knowledge:write | Add knowledge cards, and delete the ones the API added. |
| calls:read | Read calls: who rang, when, the outcome and the summary. |
| calls:transcripts | Also read what was said on a call. Kept apart because it is the caller's own speech. |
| texts:send | Send a text to a person who agreed to get one. |
| usage:read | Read this month's usage, for the organization and for each key. |
| webhooks:manage | Add, list and remove webhooks. |
Limits
What to count on
- Requests. A key can make 60 requests a minute and an organization 120 across all its keys. The counts are kept per server, so treat them as approximate. Every answer carries
X-RateLimit-Limit,X-RateLimit-RemainingandX-RateLimit-Reset, and a 429 carriesRetry-After. - Size. A request body can be 256 KB. A chat can hold 80 messages of up to 8,000 characters each. A list returns up to 100 items a page.
- Answers. Every answer counts toward the organization’s monthly answers, the same ones its Website chat uses. When they are used up the API answers 429 with
allowance_reachedand the date they start again. A key can also be given a monthly cap of its own. - A system message. A
systemordevelopermessage is folded in beneath Lina’s own rules, up to 2,000 characters in all. It can shape the tone and format of an answer; it cannot outrank the rules. - Knowledge. An organization can hold 500 cards written through the API and write 1,000 a day (cards deleted today count). A card can be up to 20,000 characters, and a text up to 1,600 characters.
- Webhooks. An organization can have 10 webhooks made through the API.
- Writes you may send twice. Add an
Idempotency-Keyheader (1 to 64 letters, digits and. _ : -) and the same request within 24 hours returns the first answer, markedIdempotent-Replayed: true. Chat and answers are not idempotent: a retry is a new answer and is counted again.
How answers arrive
Read this before you stream
- Streaming. With
stream: truethe answer is sent as server-sent events in slices of about 96 characters, once it is complete and checked. The first byte arrives when the whole answer is ready, and a client never holds text that is later changed. The stream ends withdata: [DONE]. - Settings Lina owns.
temperature,max_tokensand the other sampling settings are ignored, and named in theX-Lina-Ignored-Paramsresponse header. Tools, functions,response_format,nabove 1 andlogprobsare refused with 400unsupported_parameter. - Answers are AI output. If your software shows an answer to a person, say it came from an AI.
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_requestedcall.receivedcall.escalatedtext.receivedform.submittedemail.receivedknowledge.updatedusage.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"}}
| Code | Status | What it means |
|---|---|---|
| missing_api_key | 401 | No API key was sent. Send it as 'Authorization: Bearer <key>'. |
| invalid_api_key | 401 | The key is not valid: mistyped, revoked or expired. |
| account_paused | 402 | The organization's billing is paused, so its API is paused too. |
| payment_required | 402 | The organization has not finished signing up. |
| missing_scope | 403 | The key was not given the scope this endpoint needs. |
| module_disabled | 403 | The organization's plan does not include what this endpoint belongs to. |
| secret_key_in_browser | 403 | A 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_disabled | 403 | Reading knowledge from outside is switched off for this organization. |
| reserved_row | 403 | The card was written in the Command Center, so it can only be removed there. |
| one_to_one_only | 403 | Lina's own number texts one person at a time, so it cannot send through the API. |
| no_module | 403 | The organization's plan cannot send texts. |
| not_registered | 403 | The organization's texting is not approved by the carriers yet. |
| wrong_host | 404 | The request went to a host other than the API host. |
| forbidden | 403 | That is not allowed for this key. |
| unauthorized | 401 | Authentication failed. |
| invalid_json | 400 | The body is not valid JSON. |
| invalid_request_body | 400 | The body must be a JSON object. |
| invalid_request | 400, 422 | A parameter or the request as a whole could not be processed. |
| bad_request | 400 | The request could not be understood. |
| invalid_value | 400 | A value has the wrong type or is empty. |
| missing_required_parameter | 400 | A required parameter was not sent. |
| model_not_found | 400 | The model is not 'lina'. This API serves one model. |
| unsupported_parameter | 400 | The parameter is not supported: tools, functions, structured output and the like. |
| empty_array | 400 | A list that must hold at least one item is empty. |
| array_above_max_length | 400 | A list holds more items than the endpoint allows. |
| string_above_max_length | 400 | A text is longer than the endpoint allows. |
| invalid_cursor | 400 | The 'starting_after' value is not the id of an item in the list. |
| invalid_idempotency_key | 400 | The Idempotency-Key is not 1 to 64 letters, digits and . _ : -. |
| invalid_event | 400 | An event type does not exist. |
| invalid_url | 400 | The URL is not a public http(s) address. |
| request_too_large | 413 | The body is larger than the limit. |
| unsupported_media_type | 415 | Send the body as JSON. |
| not_found | 404 | There is no such endpoint, or no such card, call or webhook. |
| method_not_allowed | 405 | That method is not allowed on this endpoint. |
| idempotency_key_reused | 422 | The Idempotency-Key was already used for a different request. |
| idempotency_in_progress | 409 | A request with that Idempotency-Key is still being processed. |
| conflict | 409 | The request conflicts with the current state. |
| body_too_short | 422 | The card's body is too short to be a fact the assistants can use. |
| reserved_line | 422 | A line starts with DIRECTIVE:, ALWAYS:, NEVER: or PIN:, which only a person may write. |
| labeled_fact | 422 | A line names a fact the assistants keep, which only a person may set. |
| reserved_text | 422 | The text marks a note as private to the organization. |
| reserved_tag | 422 | A tag is reserved for the Command Center. |
| too_many_cards | 409 | The organization holds as many cards written through the API as it may. |
| too_many_cards_today | 409 | The organization wrote as many cards through the API today as it may. |
| too_many_webhooks | 409 | The organization has as many webhooks as it may. |
| bad_number | 422 | The number is not one a text can be sent to. |
| too_long | 422 | The text is too long. |
| no_consent | 422 | The person has not agreed to get texts from this organization. |
| opted_out | 422 | The person asked not to get texts. |
| quiet_hours | 409 | It is outside the hours texts may be sent. Try later. |
| voter_quiet_hours | 409 | It is outside the hours the recipient's area may be texted. Try later. |
| in_progress | 409 | A text with that Idempotency-Key is still being sent. |
| no_credits | 402 | The organization has no text credits left. |
| failed_unknown | 502 | The text may have been sent. Do not send it again. |
| twilio_unknown | 502 | The text may have been sent. Do not send it again. |
| text_failed | 502 | The text was not sent. |
| paused | 503 | Texting is paused for now. Try again later. |
| store_unavailable | 503 | Texting could not check its records just now. Try again. |
| twilio_error | 503 | The text carrier could not be reached just now. Try again. |
| rate_limit_exceeded | 429 | Too many requests this minute. Wait for Retry-After. |
| allowance_reached | 429 | The organization used all the answers its plan includes this month. See resets_at. |
| key_allowance_reached | 429 | The key used its own monthly cap of answers. See resets_at. |
| service_busy | 503 | Lina is at capacity for today. Try again later. |
| service_unavailable | 503 | Lina could not answer just now. Try again in a moment. |
| model_error | 502 | The assistant could not answer just now. Try again in a moment. |
| internal_error | 500 | Something went wrong on our side. |
| api_paused | 503 | The 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.