TatvaOS · API keys · People API

People API Integration Guide

Let your own software add people to your TatvaOS organisation — a student information system, an HR package, anything that already knows who joins. One request per person. They set their own password; your software never touches it.

  1. What this is
  2. Getting a key
  3. Adding a person
  4. The fields
  5. Responses
  6. Code examples
  7. What a key cannot do
  8. Security checklist
  9. What is not in this version

What this is

One HTTPS request that creates a person in your organisation, exactly as an administrator would in the console. TatvaOS gives them an address on your verified domain, applies your plan's limits, and sends them an invitation to set their own password.

It is deliberately small: one endpoint, one authentication method, and a handful of fields.

At a glance

EndpointPOST https://core.tatvaos.com/api/v1/org/people
AuthenticationAuthorization: Bearer tvk_… — a People API key
BodyJSON: localPart, displayName, recoveryEmail or recoveryPhone, and optional others
Rate limit30 requests a minute, per calling address
Success200 with the person, who has been sent an invitation
This is not the Mail API key. That one sends mail and cannot create anybody; this one creates people and cannot send mail. They are separate deliberately, so a key given to a website contact form can never create a user. See the Mail API guide.

1. Getting a key

An organisation administrator creates keys in the TatvaOS admin: Organisation → API keys → People API. They name the key for the program that will use it ("Student information system"), tick what it may do, and hand it to you. It looks like this:

tvk_m4Kp9vRt2xQn7bLw3sHy6dFz8cJaG5eU

It is shown once. TatvaOS stores only a hash with a short visible prefix, so a list can tell two keys apart. Nobody can read the key back, including us. If it is lost, the administrator revokes it and creates another.

It does only what was ticked. Today there is one thing to tick — adding people — and a key with nothing ticked can do nothing at all. An unticked permission is refused by the server, not merely hidden in the console.

2. Adding a person

One POST, JSON body, key in the Authorization header.

POST /api/v1/org/people HTTP/1.1
Host: core.tatvaos.com
Authorization: Bearer tvk_m4Kp9vRt2xQn7bLw3sHy6dFz8cJaG5eU
Content-Type: application/json

{
  "localPart":     "asha.rao",
  "displayName":   "Asha Rao",
  "recoveryEmail": "asha.rao@example.com"
}

That creates asha.rao@your-verified-domain and sends Asha an invitation at her personal address. She follows the link and chooses her own password. Nobody — not you, not your software, not the administrator — ever sets it or sees it.

Which domain

If your organisation has one verified domain, leave domainId out and TatvaOS uses it. If you have more than one, the request is refused with a message listing them and their ids, because guessing which domain a person belongs on is not a guess worth making.

3. The fields

FieldRequiredMeaning
localPartyesThe part before the @. "asha.rao" becomes asha.rao@your-domain.
displayNameyesTheir name as colleagues will see it.
recoveryEmailone ofA personal address where the invitation is sent. Stored unverified until they follow the link, which proves it.
recoveryPhoneone ofA mobile number with country code, if the invitation should go by SMS instead.
domainIdnoOnly needed when the organisation has more than one verified domain.
departmentIdnoPuts them in a department, which sets their default products.
rolenoOnly employee is accepted. See what a key cannot do.
productsnoWhich products they get, for example ["mail"]. Defaults to the department's, then to mail.
One of recoveryEmail or recoveryPhone is required. Without one there is no way to deliver an invitation, and the request is refused rather than creating an account nobody can ever enter. That is a deliberate refusal, not a validation oversight.

4. Responses

Every response is JSON. Read the status code first, then the body.

StatusMeaning
200The person exists and an invitation is on its way. The body carries their id and address.
400Something in the request, and the message says what: no way to send an invitation, an invalid local part, an unverified domain, more than one verified domain with none named, or your plan's people limit reached.
401The key is missing, malformed, unknown, or revoked. All four get the same answer on purpose — the response never reveals whether a key once existed.
403The key is real but is not allowed to add people, or the request asked for a role a key may not grant.
409That address already exists. Adding the same person twice is refused rather than silently ignored.
429More than 30 requests in a minute from your address. Wait and continue; do not retry in a tight loop.

Retries

Retry a 429 after a pause, and a network failure at once. Do not retry a 409 — the person is already there. Do not retry a 400, 401 or 403 unchanged; the same request gets the same answer.

5. Code examples

Each adds the same person. Replace the key and the addresses.

curl

curl -X POST https://core.tatvaos.com/api/v1/org/people \
  -H "Authorization: Bearer $TATVAOS_PEOPLE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"localPart":"asha.rao","displayName":"Asha Rao","recoveryEmail":"asha.rao@example.com"}'

Node.js (18+, built-in fetch)

const res = await fetch("https://core.tatvaos.com/api/v1/org/people", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.TATVAOS_PEOPLE_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    localPart: "asha.rao",
    displayName: "Asha Rao",
    recoveryEmail: "asha.rao@example.com",
  }),
});
const body = await res.json();
if (res.status === 409) return;                       // already there, nothing to do
if (!res.ok) throw new Error(`${res.status}: ${body.error}`);

Python (requests)

import os, requests

r = requests.post(
    "https://core.tatvaos.com/api/v1/org/people",
    headers={"Authorization": f"Bearer {os.environ['TATVAOS_PEOPLE_KEY']}"},
    json={
        "localPart": "asha.rao",
        "displayName": "Asha Rao",
        "recoveryEmail": "asha.rao@example.com",
    },
    timeout=30,
)
if r.status_code == 409:
    pass                                              # already there
elif not r.ok:
    raise RuntimeError(f"{r.status_code}: {r.json().get('error')}")

A joiners feed, end to end

The pattern most integrations follow. Your system holds the list of people who have joined. Nightly, or on each new record, it sends one request per person. Treat 409 as success — it means that person is already in TatvaOS, which is what you wanted — and you can re-send the whole list safely without creating duplicates.

Keep your own record of who you have sent, so a failure part-way through a batch can be resumed rather than restarted.

6. What a key cannot do

Stated plainly, because these are the questions a careful administrator asks before handing a credential to a program.

7. Security checklist

DoDon't
Keep the key in a server-side secret store or environment variable.Embed it in browser code, a mobile app, or a public repository.
Use one key per program, named for what it does.Share one key between your HR system and a partner's.
Ask the administrator to revoke a key the moment you suspect exposure. It stops working on the next request.Keep using a key that has been in a chat, a ticket, or a log.
Decide in your own system who should exist in TatvaOS, and send only those.Feed an unfiltered list straight in — every person created counts against the organisation's plan.
Treat 409 as success and re-run safely.Build a one-shot import that cannot be run twice.
A key that adds people is the most powerful credential your organisation can issue. A leaked mail key sends unwanted mail; a leaked people key creates accounts. Everything it does is written to your audit trail against the key's name, so an administrator can see what it did and when — but the first defence is where you keep it.

8. What is not in this version

Stated so you can plan around it rather than discover it.

Not yetWhat it will be
Reading peopleA list and lookup call, so your system can reconcile rather than re-send.
Suspending and offboardingEnding someone's access when they leave, which is the other half of a joiners feed.
Bulk requestsMany people in one request. Until then: one request each, within the rate limit.
Updating a personChanging a name or department through the API. Today that is the console.

Each of those is a decision about what a stolen key could do, which is why none of them has been added quietly.