/api/v1/healthThe first call to make. Confirms the key belongs to a business and says which features are on. Cheap enough to call when your server starts.
curl https://corva.tiruvi.site/api/v1/health \
-H "Authorization: Bearer $CORVA_API_KEY"Corva runs an AI assistant for each business. It answers from the business’s own knowledge, collects what the business asks for, and turns every conversation into a customer, a lead with an owner and a follow-up. This API is how a website and the business’s systems plug into that.
https://corva.tiruvi.sitecorva.tiruvi.site/llms.txt and let it read for itself.A key, two settings, and a chat route on your server.
ck_ and is shown once.CORVA_API_KEY, next to CORVA_API_URL=https://corva.tiruvi.site. These are the only two settings an integration needs. Never ship the key to the browser.GET /api/v1/health. Then read GET /api/v1/config for the assistant's name, what can be booked and the details the business collects.POST /api/v1/chat and returns the reply. When the reply carries a proposal, show it as a card; send Confirm or Edit to POST /api/v1/chat/confirm. When the customer closes the chat or reloads the page, send POST /api/v1/chat/end./voice-sessions), visitor tracking with consent (/visits), order status for the assistant to answer from (/records), payments (/payments), and a webhook so your own systems hear about new leads.// A tiny server-side client, Node / Next.js route handler.
export async function corva(path: string, body?: unknown) {
const res = await fetch(`${process.env.CORVA_API_URL}/api/v1/${path}`, {
method: body === undefined ? "GET" : "POST",
headers: {
Authorization: `Bearer ${process.env.CORVA_API_KEY}`,
"Content-Type": "application/json",
},
body: body === undefined ? undefined : JSON.stringify(body),
signal: AbortSignal.timeout(20_000),
});
if (!res.ok) throw new Error((await res.json()).error);
return res.json();
}
const { assistant, fields, booking } = await corva("config");
const turn = await corva("chat", { sessionId, message, visitorId });
// turn.reply → show it
// turn.proposal → show a card; on Confirm:
await corva("chat/confirm", { sessionId, proposalId: turn.proposal.id, approved: true });
// The window closed, or the page is unloading (sendBeacon to your own route):
await corva("chat/end", { sessionId });Sessions and visitors, when a chat ends, one customer across channels, the leads board, details, data out, bookings, takeover.
A sessionId is one chat, you make it up, reuse it for every message, and start a new one for a new chat. A visitorId is one browser, kept in a first-party cookie you set. Send it on everything and Corva joins a visitor's page views, chats, bookings and voice calls into one customer record once they say who they are.
A call hangs up; a chat window just closes. Send POST /api/v1/chat/end when it does, from a pagehide handler, with navigator.sendBeacon to your own server route, and the conversation is closed, summarised and sent to your conversation.ended webhook at once. If you do not, Corva ends it when the same visitorId starts a new sessionId, or after 30 quiet minutes. Either way that sessionId is finished: the next message answers 409, so start a new one.
Corva keeps one customer record across website chat, voice, phone, WhatsApp, email and what your system sends. Every number and email they have used is on it, each marked verified (they wrote from it on WhatsApp or email, they called from it, your system sent it, or they entered a code sent to it) or stated (typed into a chat). A browser that was identified before starts its next chat as that customer.
What you pass as customer on /chat is stated: the chat joins that record, but the assistant reads out what is on file (an address, past payments) and asks for a payment only once the customer proves who they are, it sends a six-digit code by SMS or email to the number or email already on file, never to one typed in the chat, and the model never sees the code. Records you send to /records and /payments count as verified. Two customers are never merged on a claim; a likely match goes to the team.
Five stages, the same for every business: new (New), contacted (Contacted), proposal (Processing), won (Converted), lost (Lost). A customer saying who they are makes a New lead, and so does a booking (it waits there for the customer to confirm it from its email: customerConfirmedAt); a payment reported paid makes it Converted. Move it to Processing and Converted from your own system with POST /api/v1/leads/{id}; Lost is only ever a person's call. Older leads may say qualified, read it as Processing.
Each business keeps a list of what it needs from a customer, an address, a request type, a budget. Its team edits the list in Corva; you read it from /config as fields. The assistant asks for them in chat and on calls; your own forms can show the same fields and send answers as details ({ key: value }). Every chat reply reports what has been collected so far. Do not hard-code the list: it changes without a deploy.
Two ways, and most integrations use both. Webhooks push each lead, follow-up and handoff to your system the moment it happens. The Records endpoints let you read it back whenever you like, list leads changed since your last sync, look a customer up by phone, pull a transcript. Both carry the same two things: the business's own details by field key, and request, the booking or callback as fields (date, time slot, services, address, reference) rather than a sentence. Filter on anything collected: GET /api/v1/leads?details.service_type=AC%20service.
The assistant never books on its own say-so. It returns a proposal, a booking (the business's own word for it is proposal.noun: a pickup, an appointment, a visit) or a callback, with the details to show. Nothing exists until the customer confirms; then Corva creates the customer, the lead, its owner and the follow-up, and sends the emails. If the customer types instead of tapping, the card is replaced.
Anyone on the business's team can take a chat or a call from the assistant in Corva. In a chat, replies then come from GET /api/v1/chat under that person's name. On a voice call the assistant says it is transferring the caller, and the person's voice arrives over the same connection, your page keeps playing the audio it is sent.
For your customers' data the business is the data fiduciary; Corva processes it for the business. Before the first message or call, show a short notice in the chat window: that the customer is talking to an AI assistant, what it keeps (what they type or say, their contact details), why, and a link to the business's privacy policy and to how they can ask to see, correct or delete it. When a customer asks, the business answers from Corva: Download their data and Erase this customer on the customer's record, and customer.erased tells your system to erase them too. A business whose customers may be under 18 (a school, a children's clinic) needs a parent's verifiable consent unless the law exempts what it does; the assistant cannot check age, so such a business asks the parent to make contact.
Bearer keys, status codes, rate limits, timeouts and formats.
Every request carries Authorization: Bearer ck_…. A key belongs to one business; revoking it in Settings stops it at once. Requests and responses are JSON (a streamed chat reply is text/event-stream).
| Status | Meaning |
|---|---|
200 | Done. The body is the result. |
400 | Something in the request is wrong. error says what, in words you can show a user. |
401 | The key is missing, wrong, or revoked. |
403 | The key works but lacks the scope this route needs (website, read, write or webhooks; chosen when the key is made). |
402 | The business's plan has no room for a new conversation. error is safe to show a customer; treat the assistant as offline. |
404 | No conversation or card with that id. |
409 | The chat has ended (start a new sessionId), or a card was answered or replaced already. |
413 | The body is over 64 KB. |
422 | A card can no longer be confirmed as it is, e.g. its date has passed. Show error and let them edit. |
429 | Over 300 requests a minute for this business, one chat sending more than 12 messages a minute, or more than 120 new chats an hour for this business. Back off and retry. |
5xx | Our side. Retry; /leads is safe to retry with the same reference, /chat/confirm with the same proposalId. |
Timeouts: a chat reply usually starts within two to four seconds and can take longer when the model is busy, allow 30 seconds, or stream. Phone numbers are accepted in any common Indian format and returned as +91 98765 43210. Dates are YYYY-MM-DD in India time; timestamps are ISO 8601 UTC.
Every endpoint, with its fields, a request and a response.
Base URL https://corva.tiruvi.site. Every request carries Authorization: Bearer ck_…. Example values are from a made-up home-services business ("Acme Home Services", assistant "Ava"); a real business's fields and words come from /config.
/api/v1/healthThe first call to make. Confirms the key belongs to a business and says which features are on. Cheap enough to call when your server starts.
curl https://corva.tiruvi.site/api/v1/health \
-H "Authorization: Bearer $CORVA_API_KEY"/api/v1/configEverything your site needs so it does not hard-code the business: the assistant's name, what can be booked, and fields, the business's Details to collect, which its team edits in Corva. Build your own forms from fields and send answers back as details (keyed by key); the assistant asks for the same ones in chat and on calls. Read it at start-up or cache it for a few minutes: when the business adds a field, your site picks it up without a deploy.
curl https://corva.tiruvi.site/api/v1/config \
-H "Authorization: Bearer $CORVA_API_KEY"/api/v1/chatOne message in, the assistant's reply out. It answers only from the business's knowledge (kept in Corva), stays within the limits the business set, collects the business's details as the conversation goes, and hands over to a person when it should. Keep one sessionId per chat.
When it has what a booking or a callback needs, it does not make it, it returns a proposal. Show that as a card with Confirm and Edit and send the customer's answer to POST /api/v1/chat/confirm. A new message replaces an unanswered card.
details is everything collected so far, keyed by the business's field keys. heldBy is set once a person on the team has taken the chat over: reply is then null, and their replies come from GET /api/v1/chat.
With stream: true the answer is text/event-stream: delta events ({ text }) while the reply is written, a proposal event when a card should show, then done with the same body as the JSON answer, or error.
Bookings. For a business that has set up things to book (Bookings in Corva), the assistant checks real open times and may send a slots event, { bookingTypeId, type, date, day, times: [{ time, label }] }, which you can show as buttons that send, say, "11:00 am on Wednesday, 7 Oct". Its card then has kind: "appointment" with details: { type, date, time, when, name, email?, phone?, notes? }. Confirming books the slot; if it filled in the meantime the confirm returns 409 with the times still open. Ignore events you do not recognise.
sessionIdstringrequiredYour id for this chat: 6–80 of [A-Za-z0-9_-]. Reuse it for every message in the chat; start a new one for a new chat.
messagestringrequiredWhat the customer typed. Up to 2,000 characters.
visitorIdstringYour first-party visitor id, to join the chat to their visits and later calls.
customerobject{ name?, phone?, email?, userId?, app? }, what you already know about them. For a user you authenticated, send idToken (your login's signed token; verified by Corva, recommended) or, from your server, userId (and app) after POST /accounts linked it: Corva then treats them as proven and the assistant can answer from their record without a code. A business can turn on "only trust signed tokens" and then userId alone identifies nobody. phone/email alone are only stated, not proof. The assistant will not ask again. Corva treats these as stated, not proven: the chat joins that customer's record, but the assistant reads out what is on file (an address, payments) and asks for a payment only after the customer enters a code sent to the number or email on file.
streambooleanAnswer as server-sent events, to show the reply as it is written.
curl -X POST https://corva.tiruvi.site/api/v1/chat \
-H "Authorization: Bearer $CORVA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sessionId": "chat_8c1f2a",
"message": "My AC is leaking. Can someone come tomorrow morning? I'm Riya, 98765 43210, B-402 Palm Grove, Sector 70",
"visitorId": "v_3f9c0a1b2c3d4e5f6a7b8c9d"
}'/api/v1/chat/locationCall from your server when the visitor taps your Share location button (the browser's location, or a pin on your map) after the chat has started. Corva keeps the point on the customer, or holds it on the chat until they give a name or number. Send the returned message as the visitor's next chat message so the assistant knows and carries on, the same as a location shared on WhatsApp. consent must be true: show a line saying why you ask. With saveAs (Home, Work) and an address, it is also saved as one of the customer's addresses.
sessionIdstringrequiredThe chat's sessionId.
latnumberrequiredLatitude.
lngnumberrequiredLongitude.
consentbooleanrequiredTrue: the visitor agreed to share it.
sourcestringgps (the browser's location) or pin (dropped on a map). Default pin.
accuracyMnumberThe browser's accuracy in metres, for gps.
addressstringThe readable address for the point, if your map gave one.
pincodestringIts six-digit PIN code, if known.
saveAsstringSave as a named address: Home, Work. Needs address.
curl -X POST https://corva.tiruvi.site/api/v1/chat/location \
-H "Authorization: Bearer $CORVA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sessionId": "web-3f9a1c",
"lat": 28.4089,
"lng": 77.0507,
"accuracyM": 18,
"source": "gps",
"consent": true,
"address": "B-204, Palm Court, Sector 57, Gurugram",
"pincode": "122003",
"saveAs": "Home"
}'/api/v1/chat/confirmConfirm makes what the card shows: the customer's record, a lead with an owner on the team, a follow-up on that person's list, a confirmation email to the customer and a note to the owner. reply is the line to show in the chat and receipt is for the card. Edit tells the assistant they want to change something, show the reply and let them type. Confirming the same card twice returns the first result with duplicate: true.
sessionIdstringrequiredThe chat's sessionId.
proposalIdstringrequiredproposal.id from the chat reply.
approvedbooleanrequiredtrue for Confirm, false for Edit.
visitorIdstringYour visitor id, to join the booking to their visits.
curl -X POST https://corva.tiruvi.site/api/v1/chat/confirm \
-H "Authorization: Bearer $CORVA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sessionId": "chat_8c1f2a",
"proposalId": "5f0e…",
"approved": true,
"visitorId": "v_3f9c0a1b2c3d4e5f6a7b8c9d"
}'/api/v1/chatAnyone on the business's team can take a chat over from the assistant in Corva. Poll this, every eight seconds or so while a chat is open, every three once heldBy is set, and show messages whose from is not "assistant" under that person's name. heldBy goes back to null when they hand the chat back; ended means start a new sessionId.
sessionIdstringrequiredThe chat's sessionId.
afternumberOnly messages after this ordinal. Start at -1, then pass the highest ordinal you have seen.
curl https://corva.tiruvi.site/api/v1/chat?sessionId=…&after=… \
-H "Authorization: Bearer $CORVA_API_KEY"/api/v1/chat/endA website chat has no hang-up, so tell Corva when it is over: the chat window was closed, the page was reloaded, or the customer started a new chat. The conversation is closed at once, resolved if the customer said anything, abandoned if not, and summarised, and conversation.ended goes to your webhooks. Use navigator.sendBeacon to your own server route on pagehide, which then calls this with the key.
If you never call it, nothing breaks: a chat is also ended when the same visitorId starts a new sessionId, or after 30 quiet minutes. Ending a chat that is already over, or a sessionId Corva has not seen, does nothing (ended: false). After it, that sessionId answers 409: start a new one.
sessionIdstringrequiredThe chat's sessionId.
curl -X POST https://corva.tiruvi.site/api/v1/chat/end \
-H "Authorization: Bearer $CORVA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sessionId": "chat_8c1f2a"
}'/api/v1/chatsOnly if your site runs its own chat assistant instead of Corva's. Send the whole transcript after each reply; Corva keeps one conversation per sessionId, replacing it each time, so the team can read every chat live and see where a request came from.
sessionIdstringrequiredYour chat id.
visitorIdstringYour visitor id.
messages{ role: "user" | "assistant", text: string }[]requiredThe whole conversation so far.
curl -X POST https://corva.tiruvi.site/api/v1/chats \
-H "Authorization: Bearer $CORVA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sessionId": "chat_8c1f2a",
"visitorId": "v_3f9c0a1b2c3d4e5f6a7b8c9d",
"messages": [
{
"role": "user",
"text": "Do you service window ACs?"
},
{
"role": "assistant",
"text": "Yes, window and split units both."
}
]
}'/api/v1/leadsFor your own forms, a booking form, a "call me back" button, a contact page. Corva finds or creates the customer by phone, opens (or updates) their lead with an owner on the team, puts a follow-up on that person's list, emails the customer a confirmation and tells the owner. kind: "booking" is whatever the business books (see booking.noun in /config). Every request makes the lead New; a booking with an email gets a confirmation email with a link where the customer confirms it, corrects it and shares their location, after which the lead's customerConfirmedAt is set and lead.updated is sent.
Send the business's own fields as details, keyed by the keys from /config, unknown keys are ignored, choices are matched to the business's options. Safe to retry: the same reference again returns the first result with duplicate: true.
kind"booking" | "callback" | "enquiry"requiredWhat they asked for. ("pickup" is accepted as another word for "booking".)
namestringrequiredTheir name.
phonestringrequiredTheir mobile, in any common Indian format.
emailstringWhere the confirmation goes. Strongly recommended.
referencestringYour reference for this request, also the idempotency key. Corva makes one if you do not.
detailsobjectThe business's Details to collect: { key: value }, keys from GET /config.
servicesstring[]What they want done, for a booking.
addressstringWhere, when the booking needs an address.
pincodestringSix digits (also read from the end of address). For a business that serves by PIN code, a PIN code it does not serve is refused with a 400; the PIN code and its store are kept on the customer.
datestringFor a booking: YYYY-MM-DD.
timeSlotstringFor a booking, e.g. "8–10 AM".
preferredTimestringFor a callback, e.g. "today after 6 PM".
topicstringWhat they want to talk about.
notesstringAnything else.
promoCodestringAn offer code they used.
visitorIdstringYour visitor id, to join the request to their visits.
sessionIdstringThe chat it came from, if your own assistant took it (see /chats).
curl -X POST https://corva.tiruvi.site/api/v1/leads \
-H "Authorization: Bearer $CORVA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"kind": "booking",
"name": "Riya Sharma",
"phone": "98765 43210",
"email": "riya@example.com",
"reference": "AH-7K3QX9",
"services": [
"AC service"
],
"address": "B-402, Palm Grove, Sector 70",
"date": "2026-10-04",
"timeSlot": "8–10 AM",
"details": {
"service_type": "AC service",
"units": "2 split ACs"
},
"visitorId": "v_3f9c0a1b2c3d4e5f6a7b8c9d"
}'/api/v1/leadsThe business's leads, newest-changed first, whichever way they arrived: chat, a call, your forms, or added by hand. Each carries details (the business's own Details to collect, by field key) and request (the latest booking, callback or enquiry as fields: date, time slot, services, address, reference).
Filter on any collected detail with details.<key>=<value>, keys from /config, matched case-insensitively; details.<key>= with no value means "has an answer". Because details are stored by key, a field the business adds tomorrow is filterable tomorrow with no change here.
Page with cursor: pass back nextCursor until it is null. For a sync, poll updatedSince with the time of your last run.
stagestringOne or more of new, contacted, proposal, won, lost, comma-separated (New, Contacted, Processing, Converted, Lost on the board). qualified, an older key, is accepted and counts as proposal; older leads may still come back with stage: "qualified", treat it as Processing. stageLabel is the board's word for the stage.
sincestringCreated at or after this time (ISO 8601).
updatedSincestringChanged at or after this time, what a sync asks for.
details.<key>stringA collected detail equals this, e.g. details.service_type=AC service. Repeat for several.
phonestringThe lead's phone number, any format.
referencestringThe reference a booking or callback was given.
qstringText in the name, the request or the notes.
limitnumber1–100, default 25.
cursorstringnextCursor from the previous page.
curl https://corva.tiruvi.site/api/v1/leads?stage=…&since=…&updatedSince=… \
-H "Authorization: Bearer $CORVA_API_KEY"/api/v1/leads/{id}A lead with its customer and its follow-ups, who owes the customer what, by when, and whether it was done.
curl https://corva.tiruvi.site/api/v1/leads/{id} \
-H "Authorization: Bearer $CORVA_API_KEY"/api/v1/leads/{id}Keep Corva in step with your own system: when work starts, move the lead to proposal (Processing); when it is done, to won (Converted); add what your team learnt. Send only what changes. Returns the lead as GET does, and tells webhooks (lead.updated) like a change made in the console.
The board is the same five stages for every business. Corva moves a lead itself in three places: a customer telling the assistant who they are makes it New, a confirmed booking makes it Contacted, and a payment reported paid makes it Converted. Lost is only ever a person's (or your system's) decision.
stagestringnew, contacted, proposal (Processing), won (Converted) or lost. qualified is accepted as proposal.
lostReasonstringWhy, when the stage is lost.
notesstringReplaces the lead's notes.
valueRupeesnumberWhat the lead is worth, in rupees; null to clear.
detailsobject{ key: value }, merged into the lead's details; keys from GET /config.
curl -X POST https://corva.tiruvi.site/api/v1/leads/{id} \
-H "Authorization: Bearer $CORVA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"stage": "won",
"valueRupees": 1800,
"details": {
"units": "2 split ACs"
}
}'/api/v1/recordsWhat happens after a booking happens in your system: collected, in progress, out for delivery. Send each order's current state here whenever it changes, under the reference the customer was given, and the assistant can answer "where is my order?" in chat and on calls. The same kind and reference replace what was there. status is read to the customer as it is, write it as a sentence. Name the customer by customer.id, leadId or customer.phone.
referencestringrequiredThe id the customer was given.
labelstringrequiredWhat it is, in a few words.
statusstringWhere it has got to, as a sentence the customer can be told.
kindstringorder (default), booking, delivery, ticket, invoice or subscription.
amountRupeesnumberThe bill, when there is one.
leadIdstringThe lead it grew out of.
customerobject{ id } or { phone, name?, email? }, found by phone, or created.
metaobjectAnything else worth knowing, as fields: stage, expected times, driver. The assistant may use it; never put an address or phone number here.
occurredAtstringWhen the record began; defaults to now.
curl -X POST https://corva.tiruvi.site/api/v1/records \
-H "Authorization: Bearer $CORVA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"reference": "AH-7K3QX9",
"label": "AC service",
"status": "The technician is on the way, expected around 5:40 pm.",
"leadId": "8f3b2c1a-…",
"meta": {
"stage": "en_route",
"expectedArrival": "2026-10-04T12:10:00.000Z"
}
}'/api/v1/recordsWhat Corva currently holds under a reference, what the assistant would tell a customer who asked.
referencestringThe reference, in any spacing or case.
curl https://corva.tiruvi.site/api/v1/records?reference=… \
-H "Authorization: Bearer $CORVA_API_KEY"/api/v1/noticesTell a customer their order has moved (pickup booked, work started, ready, payment due) on WhatsApp and by email, from the business's own number and sender. You write the message; Corva delivers it. WhatsApp only lets a business write freely within 24 hours of the customer's last message, so outside that window the message goes by email only (the response says which channels went). The same key sends once, however often you post it. Name the customer by customer.id, leadId, customer.phone or customer.email.
keystringrequiredYour own id for this message, such as "TD-7K3QX9:ready". Posting it again sends nothing.
kindstringrequiredpickup, started, ready, payment_due or bill.
textstringrequiredThe message, as the customer should read it (600 characters at most).
referencestringThe order reference, shown in the email's subject.
linkstringAn https address: the tracking page, or the payment link for payment_due. Added to the message.
linkLabelstringThe email button's words (30 characters), such as "Pay now"; defaults to one that fits the kind.
customerobject{ id } or { phone, email?, name? }.
curl -X POST https://corva.tiruvi.site/api/v1/notices \
-H "Authorization: Bearer $CORVA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"key": "TD-7K3QX9:ready",
"kind": "ready",
"reference": "TD-7K3QX9",
"text": "Hi Asha, your order TD-7K3QX9 is ready.",
"link": "https://orbo.tiruvi.site/t/…",
"customer": {
"phone": "+919811100777",
"email": "asha@example.com"
}
}'/api/v1/summaryTotals for the last days days (1–90, default 7): customers who reached out and how many were new, conversations and who answered them (the assistant or the team) by channel and by day, leads opened, won and lost, follow-ups overdue and due today, handoffs waiting, and each person's calls, chats, follow-ups and wins. For an owner's dashboard on the business's own site; counts only, no transcripts or customer names. Every figure matches Corva's Overview screen, linked in links.
daysnumberHow many days back, 1–90. Default 7.
curl https://corva.tiruvi.site/api/v1/summary?days=… \
-H "Authorization: Bearer $CORVA_API_KEY"/api/v1/teamEveryone with access to the business: name, email, phone, the role they hold in Corva and their status. customRole is the name of their custom role in Corva, if any, and corvaAccess is false when that role does not use Corva at all (a driver, a cleaner). When the business uses Orbo for orders and deliveries, delivery says what the person is there, driver, staff, store_manager or regional_manager, and places, the codes of their stores (region codes for a regional manager); via says whether it was set on the person or comes from their Corva role. It is null for people with no role in Orbo. Orbo reads this so a business adds a person once, in Corva. People are added and given a delivery role on Corva's Team screen.
curl https://corva.tiruvi.site/api/v1/team \
-H "Authorization: Bearer $CORVA_API_KEY"/api/v1/catalogThe business's price list in the same shape as the downloadable template: category, name, price (rupees, null when quoted), unit, description, available, sku, gstPercent, hsn, priceIncludesGst. Needs the read scope. Orbo reads this to stay in step.
curl https://corva.tiruvi.site/api/v1/catalog \
-H "Authorization: Bearer $CORVA_API_KEY"/api/v1/catalogSend the list in the template's shape. Items are matched by category and name, so sending again updates prices instead of doubling the list; nothing is ever removed. At most 2,000 at a time. Needs the write scope. Orbo sends its list here.
itemsarrayrequiredEach: { category?, name, price?, unit?, description?, available?, sku?, gstPercent?, hsn?, priceIncludesGst? }, the template's columns.
curl -X POST https://corva.tiruvi.site/api/v1/catalog \
-H "Authorization: Bearer $CORVA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"category": "Wash & fold",
"name": "Wash and fold",
"price": 99,
"unit": "per kg",
"available": true
}
]
}'/api/v1/customersEveryone the business has a record for, newest first. Look one up by phone or email to recognise a returning customer in your own app, these match the number and email on file. Corva keeps one customer across every channel (chat, calls, WhatsApp, email, your records): a number or email someone only typed in a chat is kept on their record as stated but never becomes the one on file, and two customers are never merged on a claim alone.
phonestringTheir phone number, any format.
emailstringTheir email address.
qstringText in the name, email or phone.
sincestringCreated at or after this time.
limitnumber1–100, default 25.
cursorstringnextCursor from the previous page.
curl https://corva.tiruvi.site/api/v1/customers?phone=…&email=…&q=… \
-H "Authorization: Bearer $CORVA_API_KEY"/api/v1/customers/emailVerification codes go only to an email the business has confirmed for the customer, so a customer with only a phone number cannot be sent one. This emails them a single-use link; pressing its button confirms that address for them.
Call it only for a person your own system has already identified (someone at your counter, a user signed in to your app). The link proves the mailbox, not who is asking: Corva trusts you for that, as it does for phoneVerified. Never call it for an anonymous visitor who typed a phone number, or anyone could confirm their own email on someone else's record. The answer is the same whether or not anyone matched.
customerIdstringCorva's customer id (from POST /accounts or POST /customers/verify). Use this, or claim.
claimstringA phone number or email already on the customer's record.
emailstringrequiredThe address to confirm.
curl -X POST https://corva.tiruvi.site/api/v1/customers/email \
-H "Authorization: Bearer $CORVA_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'/api/v1/accountsCorva is not a login provider and does not need to be: keep whichever login you already use (your own, Firebase, Auth0, Clerk, Cognito, social sign-in) and, from your server, tell Corva when a person signs up, signs in or changes their details in your website or mobile app. Corva ties your user to the one customer it already holds (their calls, chats, enquiries, orders) or makes one, and answers with what it knows, so your app can say "welcome back" and everything they do next lands on the same record.
How the customer is found. An account already linked is the customer. Otherwise a phone or email you mark phoneVerified / emailVerified (you sent the OTP or the link and they proved it) finds the customer it belongs to and joins their whole history. A number or email you do not mark verified never joins an existing customer, because typing a number is not proof: a new record is made and a match is left for the team (needsReview). Send visitorId (your first-party visitor id) and sessionIds (chats they had before signing in) to attach those anonymous conversations to them. convertLeads: true on event: "signed_up" marks their open leads Converted.
Safe to call on every sign-in: it is idempotent per (app, userId). Needs a key with the website or write scope; never call it from a browser.
userIdstringrequiredYour own id for this person (up to 128 characters; never shown to customers). May be left out when profile.sub carries it.
customerIdstringCorva's own customer id, when your sign-in was proven by POST /customers/verify (its code) and you got the id back. The account is linked to exactly that customer. Use with a stable userId (it may be the same id).
idTokenstringThe ID token (JWT) your login issued for this person. Verified against the issuer set up in Settings → Your login; it replaces everything claimed in the body: userId is its sub, and the phone and email are verified only when the token's own phone_number_verified / email_verified say so. This is the recommended way: nothing is taken on a field's word. A business can require it (Settings → Your login).
profileobjectWhatever your own login already knows about them, in your field names or the standard OIDC claims (sub, name, given_name, family_name, email, email_verified, phone_number, phone_number_verified, address…). Corva maps the ones it knows (name, phone, email, address, the two verified claims), keeps the rest as text on the account, and drops anything that looks like a secret (password, token, otp, card…). Fields you send explicitly win.
appstringWhich of your products: "app" (default), "web", "ios"… 1–30 letters, digits, . _ -. One customer can have several.
eventstring"signed_up", "signed_in" (default) or "updated" (details changed; not counted as a sign-in).
namestringTheir name, used only to fill a record that has none.
phonestringTheir phone number, any common format.
emailstringTheir email address.
phoneVerifiedbooleantrue only when your system proved they hold this number (an OTP you sent).
emailVerifiedbooleantrue only when your system proved they hold this address (a link you sent).
visitorIdstringThe visitor id you send to POST /visits, so the anonymous browser and its chats join the account.
sessionIdsarrayUp to 10 chat sessionIds they used before signing in.
convertLeadsbooleanWith event "signed_up": mark their open leads Converted.
curl -X POST https://corva.tiruvi.site/api/v1/accounts \
-H "Authorization: Bearer $CORVA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"userId": "usr_8f31",
"app": "app",
"event": "signed_up",
"name": "Asha Verma",
"phone": "+91 98111 20456",
"phoneVerified": true,
"email": "asha@example.com",
"visitorId": "v_7d2a91c4e0",
"sessionIds": [
"c4f1e2a9b7"
],
"convertLeads": true
}'/api/v1/accounts/batchThe backfill for a business that adds Corva after it has users: up to 100 accounts a call, each linked to the customer Corva already holds by the same rules as a sign-in (a number or email you mark verified joins the history; anything else is made a new customer with a match left for the team). No sign-in is counted and no webhook is sent. signedUpAt keeps their real sign-up date. Safe to run again.
accountsarrayrequiredEach: { userId, app?, name?, phone?, email?, phoneVerified?, emailVerified?, signedUpAt?, profile? }. A token is not used in an import.
curl -X POST https://corva.tiruvi.site/api/v1/accounts/batch \
-H "Authorization: Bearer $CORVA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"accounts": [
{
"userId": "usr_1",
"name": "Asha Verma",
"phone": "+91 98111 20456",
"phoneVerified": true,
"signedUpAt": "2025-02-11T09:00:00Z"
}
]
}'/api/v1/accountsThe unified view of a user you registered: their customer, every sign-in linked to it (app and website), what Corva knows (conversations, leads, orders), their open leads and their latest order records. For your own screens; call it from your server.
userIdstringrequiredYour id for the person.
appstringDefault "app".
curl https://corva.tiruvi.site/api/v1/accounts?userId=…&app=… \
-H "Authorization: Bearer $CORVA_API_KEY"/api/v1/accountsSay a user closed or deleted their account with you. The sign-in stops identifying anyone in chats; the customer and their history stay (a person who is still a customer of the business). To erase a person's data, use the business's data-request process.
userIdstringrequiredYour id for the person.
appstringDefault "app".
curl -X DELETE https://corva.tiruvi.site/api/v1/accounts \
-H "Authorization: Bearer $CORVA_API_KEY"/api/v1/customers/{id}/addressesHome, Work and any others the customer saved, the default first. The default is also what the customer's location, PIN code and exact point show, and what a booking is sent with.
curl https://corva.tiruvi.site/api/v1/customers/{id}/addresses \
-H "Authorization: Bearer $CORVA_API_KEY"/api/v1/customers/{id}/addressesSaves an address under a label; the same label again replaces it. The customer's first address becomes their default; send makeDefault: true to switch. Up to 10 per customer. The response says whether the PIN code is one the business serves.
labelstringrequiredHome, Work, or a name of their own.
addressstringrequiredThe full address.
pincodestringSix digits.
latnumberExact point, with lng.
lngnumberExact point, with lat.
accuracyMnumberAccuracy in metres.
sourcestringtyped (default), gps, pin, whatsapp_pin or map_link.
makeDefaultbooleanUse this one from now on.
curl -X POST https://corva.tiruvi.site/api/v1/customers/{id}/addresses \
-H "Authorization: Bearer $CORVA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"label": "Work",
"address": "Tower B, Cyber Hub, DLF Phase 3, Gurugram",
"pincode": "122002",
"lat": 28.495,
"lng": 77.089,
"source": "pin"
}'/api/v1/customers/{id}/addresses/{addressId}The customer's record, PIN code and store follow it.
curl -X POST https://corva.tiruvi.site/api/v1/customers/{id}/addresses/{addressId} \
-H "Authorization: Bearer $CORVA_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'/api/v1/customers/{id}/addresses/{addressId}If it was the default, the oldest remaining address takes its place.
curl -X DELETE https://corva.tiruvi.site/api/v1/customers/{id}/addresses/{addressId} \
-H "Authorization: Bearer $CORVA_API_KEY"/api/v1/customers/{id}A customer with everything collected about them (details: the newest answer to each of the business's fields, across all their leads), their leads and their conversations.
curl https://corva.tiruvi.site/api/v1/customers/{id} \
-H "Authorization: Bearer $CORVA_API_KEY"/api/v1/customers/verifyFor a connected system's staff screen. { claim } (a phone, an email or an order reference) sends a 6-digit code to the contact the business already has for that customer, never to one typed now; the answer is the same whether or not anyone matched. { claim, code } checks what the customer read out: a right code returns the customer, their details, leads and conversations (the shape of GET /customers/{id}); a wrong one returns verified: false. Ten minutes, five tries, six codes per contact a day.
claimstringrequiredA phone number, an email or an order reference.
codestringThe 6 digits the customer read out. Leave out to send a code.
curl -X POST https://corva.tiruvi.site/api/v1/customers/verify \
-H "Authorization: Bearer $CORVA_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'/api/v1/conversationsConversations, newest first, each with the details it collected. The owner's own test conversations are left out unless you ask for them.
channelstring"chat" or "voice".
sincestringStarted at or after this time.
sessionIdstringThe chat your site started under this sessionId.
customerIdstringOne customer's conversations.
details.<key>stringA detail collected in the conversation equals this.
includeTestsstring"true" to include conversations from Try it.
limitnumber1–100, default 25.
cursorstringnextCursor from the previous page.
curl https://corva.tiruvi.site/api/v1/conversations?channel=…&since=…&sessionId=… \
-H "Authorization: Bearer $CORVA_API_KEY"/api/v1/conversations/{id}from is "customer", "assistant", "system" (an event: a takeover, the call ending) or the name of the person on the team who spoke.
curl https://corva.tiruvi.site/api/v1/conversations/{id} \
-H "Authorization: Bearer $CORVA_API_KEY"/api/v1/paymentsYour system collects the money (your own payment account, Corva never holds the keys). Send each payment's state here whenever it changes: when the link is made, when it is paid, when it expires. The same reference replaces what was there, and a status never moves backwards, so a late expired cannot undo a paid. When it becomes paid, the conversation it was asked in says so, on WhatsApp the customer is thanked, and the lead it was for (that conversation's, else the customer's open one) moves to Converted. Echo requestId and conversationId when Corva asked for the link (see Payments below).
referencestringrequiredThe payment's id in your system.
statusstringpending (default), partially_paid, paid, expired, cancelled or failed.
amountRupeesnumberWhat was asked for. Required the first time.
amountPaidRupeesnumberWhat has been paid so far.
descriptionstringWhat it is for, as the customer sees it.
urlstringThe link the customer pays at.
pageUrlstringYour own page for it, if any.
qrUrlstringAn image of a QR for the link, if any.
methodstringupi, card, netbanking … once paid.
paidAtstringWhen it was paid; defaults to when Corva hears.
expiresAtstringWhen the link stops working.
orderReferencestringThe order it pays for.
requestIdstringCorva's id for the request, when Corva asked.
conversationIdstringThe conversation it was asked in, when Corva asked.
customerobject{ id } or { phone, name?, email? }, when Corva did not ask.
curl -X POST https://corva.tiruvi.site/api/v1/payments \
-H "Authorization: Bearer $CORVA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"reference": "PAY-7K3QX9",
"status": "paid",
"amountRupees": 150,
"amountPaidRupees": 150,
"method": "upi",
"orderReference": "AH-7K3QX9",
"requestId": "cpr_5c1f…"
}'/api/v1/paymentsBy your reference, or every payment of one customer, newest first.
referencestringYour payment's reference.
customerIdstringA customer's id.
curl https://corva.tiruvi.site/api/v1/payments?reference=…&customerId=… \
-H "Authorization: Bearer $CORVA_API_KEY"/api/v1/webhooksEach address Corva sends events to, with the events it gets and how its last delivery went. Secrets are never listed: one is shown once, when the webhook is registered.
curl https://corva.tiruvi.site/api/v1/webhooks \
-H "Authorization: Bearer $CORVA_API_KEY"/api/v1/webhooksRegister an address for events without opening the console, so a system you connect can set up its own. The answer carries the signing secret, once, keep it to verify Corva-Signature on each delivery (see Webhooks above). Registering an address the business already has keeps one webhook: it gets a new secret and the events you send. Five webhooks at most; the address must be public and https.
urlstringrequiredWhere to POST events. https, and reachable from the internet.
eventsstring[]Which events to send. Omit for all of them.
namestringWho is registering it, shown beside it in Settings, "Orbo", your system's name. Default "API".
curl -X POST https://corva.tiruvi.site/api/v1/webhooks \
-H "Authorization: Bearer $CORVA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-site.com/api/corva-webhook",
"events": [
"lead.created",
"conversation.ended"
],
"name": "Our CRM"
}'/api/v1/webhooks/{id}Stop sending events to an address. Anything of yours that depended on them stops hearing about changes.
curl -X DELETE https://corva.tiruvi.site/api/v1/webhooks/{id} \
-H "Authorization: Bearer $CORVA_API_KEY"/api/v1/visitsTell Corva a visitor is on the site. With consent: "necessary" only the visitor id is kept. With "all" (they accepted analytics cookies), the page, referrer and utm_ campaign are kept too, and show on the customer's record once they identify themselves, so the team sees that this lead came from an Instagram ad. Always send the consent your banner actually recorded.
visitorIdstringrequiredYour first-party visitor id (8–80 chars).
consent"necessary" | "all"requiredWhat they chose on your cookie banner.
typestring"page_view" (default) or "consent".
pathstringOnly kept with consent "all".
referrerstringOnly kept with consent "all".
utmobjectutm_source, utm_medium, utm_campaign… Only kept with consent "all".
curl -X POST https://corva.tiruvi.site/api/v1/visits \
-H "Authorization: Bearer $CORVA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"visitorId": "v_3f9c0a1b2c3d4e5f6a7b8c9d",
"consent": "all",
"type": "page_view",
"path": "/",
"referrer": "https://instagram.com",
"utm": {
"utm_source": "instagram"
}
}'/api/v1/voice-sessionsYour server asks for a token (the API key must never reach the browser) and hands token and bridgeUrl to the page, which opens a WebSocket to the bridge and speaks to the assistant. Tokens last five minutes and start one call. The protocol is under “Voice calls in the browser”.
visitorIdstringYour visitor id.
namestringTheir name, if known, the assistant greets them by it.
phonestringTheir number, if known, joins the call to their record.
curl -X POST https://corva.tiruvi.site/api/v1/voice-sessions \
-H "Authorization: Bearer $CORVA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"visitorId": "v_3f9c0a1b2c3d4e5f6a7b8c9d",
"name": "Riya Sharma",
"phone": "9876543210"
}'Tokens, the WebSocket protocol, iPhone gotchas and limits.
Your server gets a token from POST /api/v1/voice-sessions and gives the page token and bridgeUrl. The page opens a WebSocket to the bridge and speaks. Calls are push-to-talk: the visitor holds a button (or the space bar) while speaking and lets go to hear the answer. The conversation, the details collected and anything booked land in the business's console like a phone call.
→ connect new WebSocket(bridgeUrl)
→ send {"type":"start","token":"…"}
← receive {"type":"ready","agent":"Ava","capSeconds":180, …}
while the visitor holds "talk":
→ send binary frames, PCM16, mono, 16 kHz
when they let go:
→ send {"type":"end_turn"}
← receive binary frames, PCM16, mono, 24 kHz: play them in order
← receive {"type":"heard","text":"…"} what the visitor said, so far
← receive {"type":"said","text":"…"} what the assistant said, so far
← receive {"type":"turn_complete"}
a person on the team takes the call:
← receive {"type":"held","by":"Kavya Rao"} drop any assistant audio still queued
the assistant says one line handing over; then the person's voice
arrives as the same 24 kHz binary frames, keep playing them
← receive {"type":"human","name":"…","text":"…"} what they said or typed, as a caption
← receive {"type":"released"} handed back to the assistant
← receive {"type":"error","message":"…"} show it; the call is over
← receive {"type":"closed","reason":"…","seconds":142}
→ send {"type":"stop"} hang upBrowsers, and iPhones in particular. The microphone only works on an https page (or localhost), on plain http, Safari never asks for permission and navigator.mediaDevices is missing. Ask for the microphone and create your AudioContext in the tap handler itself, before any await, or iOS will neither prompt nor play sound. Use the device's own sample rate and resample to 16 kHz yourself; iOS does not honour a requested rate. And the bridge must be wss:// for an https page, /health tells you (features.voiceSecure); hide the call button when it is false.
Limits. An unattended call with the assistant ends at capSeconds; a call a person has taken over can run longer. A call with no audio from the visitor for 45 seconds is closed.
Events Corva sends your systems, and how to verify each delivery.
The API is your site talking to Corva; a webhook is Corva telling your systems what just happened, to put a new lead into your own CRM or order system, post it to a team channel, or start a job. The business adds a URL in Corva → Settings → Webhooks, chooses the events, and is shown a signing secret (whsec_…) once. A system can do the same with its key, POST /api/v1/webhooks answers with the secret, once, which is how Orbo connects without anything being pasted.
| Event | When |
|---|---|
lead.created | A new lead, from a call, a chat, your website's form, or added by hand. |
lead.updated | A lead changed: new details, a new stage, a new owner. |
lead.converted | A lead was converted: everything gathered about the customer and what they asked for, in one payload, so your own system can place the order. Sent a minute after a person converts it, unless they confirm sooner or put it back. |
follow_up.created | Someone on the team now owes the customer something, by a time. |
handoff.requested | The AI needs a person: it reached a limit, or the customer asked for one. |
conversation.ended | A call or chat finished, with its outcome and the details collected. |
customer.created | A customer Corva did not have before: from an account your app registered, a call, a chat or an email. |
customer.erased | The business erased a customer at their request. Only the ids: erase the person on your side too. |
customer.linked | Your app's sign-in was tied to a customer Corva already knew: someone who had called, chatted or enquired before. |
Each delivery is one POST with a JSON body and two headers: Corva-Event (the type) and Corva-Signature. The URL must be https on a public address and answer itself, a redirect is not followed. Answer 2xx within six seconds. A 5xx or no answer is retried once; treat webhooks as notifications and the API as the record, and be ready to receive the same event twice (id is unique per event).
{
"id": "evt_3f9c0a1b2c3d4e5f6a7b8c9d",
"type": "lead.created",
"createdAt": "2026-10-03T04:31:13.000Z",
"data": {
"lead": {
"id": "c505…",
"name": "Riya Sharma",
"phone": "+91 98765 43210",
"email": "riya@example.com",
"interest": "AC service · visit 2026-10-04, 8–10 AM · B-402, Palm Grove, Sector 70",
"notes": "Ref AH-7K3QX9",
"stage": "new",
"stageLabel": "New",
"valueRupees": null,
"source": "web_chat",
"createdByAi": true,
"owner": "Kavya Rao",
"details": {
"address": "B-402, Palm Grove, Sector 70",
"service_type": "AC service",
"units": "2 split ACs"
},
"request": {
"kind": "booking",
"reference": "AH-7K3QX9",
"services": [
"AC service"
],
"address": "B-402, Palm Grove, Sector 70",
"date": "2026-10-04",
"timeSlot": "8–10 AM"
},
"customerConfirmedAt": "2026-10-03T11:12:00.000Z",
"customerId": "a734…",
"conversationId": "0b7c…",
"createdAt": "2026-10-03T04:31:12.000Z",
"updatedAt": "2026-10-03T04:31:12.000Z"
},
"customer": {
"id": "a734…",
"name": "Riya Sharma",
"phone": "+91 98765 43210",
"email": "riya@example.com"
},
"request": {
"kind": "booking",
"reference": "AH-7K3QX9",
"services": [
"AC service"
],
"address": "B-402, Palm Grove, Sector 70",
"date": "2026-10-04",
"timeSlot": "8–10 AM"
}
}
}Verify every delivery. The signature is an HMAC-SHA256 of "<t>.<raw body>" with your secret, hex-encoded. Use the raw request body, exactly as received, and reject a timestamp more than five minutes old.
import { createHmac, timingSafeEqual } from "node:crypto";
// Next.js route handler: app/api/corva-webhook/route.ts
export async function POST(req: Request) {
const body = await req.text(); // raw, before any JSON.parse
const header = req.headers.get("corva-signature") ?? "";
const t = header.match(/t=(\d+)/)?.[1];
const v1 = header.match(/v1=([0-9a-f]+)/)?.[1];
if (!t || !v1 || Math.abs(Date.now() / 1000 - Number(t)) > 300) return new Response("stale", { status: 400 });
const expected = createHmac("sha256", process.env.CORVA_WEBHOOK_SECRET!).update(`${t}.${body}`).digest("hex");
if (expected.length !== v1.length || !timingSafeEqual(Buffer.from(expected), Buffer.from(v1))) {
return new Response("bad signature", { status: 401 });
}
const event = JSON.parse(body);
if (event.type === "lead.created") await addToOurSystem(event.data.lead, event.data.customer);
return new Response("ok");
}Send test in Settings posts a ping event to your URL and shows what it answered. The other payloads, lead.updated, lead.converted, follow_up.created, handoff.requested, conversation.ended, are in the OpenAPI spec under webhooks.
Placing the order in your own system. Corva keeps customers, conversations and leads, not orders (Orbo does, when it is on). Register your URL for lead.converted: it is sent once when a lead is converted, with the customer, what they asked for (order.items), the address and map pin, the slot, the notes, what was paid and the lead's own details, so your system can make the order. A person's move to Converted or Lost is held for a minute (they can put it back or confirm sooner); a payment reported paid is sent at once. Orbo keeps working from lead.updated and is not affected.
Your payment endpoint, offers checked in code, and reporting a payment back.
The team in the console, and the assistant on a chat, WhatsApp or a call, can ask a customer to pay. The money is the business's: your system makes the payment link with your payment account (Razorpay, or any other), so no payment key ever reaches Corva. The business sets your endpoint in Corva → Settings → Payments and is shown a signing secret (cps_…) once.
When a payment is asked for, Corva sends your endpoint one POST, signed exactly like a webhook (Corva-Signature, verified the same way as above, with this secret). Answer within 15 seconds with the payment as you made it, the same fields you would send to POST /api/v1/payments. The assistant only ever sends an orderReference: it never names an amount, so work out what is owed from your own order. A person in the console may send amountRupees. If you cannot make a link, answer 4xx with { "error": "…" } in words the customer may be told ("There is no bill on this order yet"). The same requestId twice must return the same link.
Discounts never come from the model. A business publishes its offers in Corva (a code, a percentage with a cap or a flat amount, a minimum order, dates, first order only, once per customer); when a customer names one, Corva checks it in code and, only if it applies, sends it as offer. Take it off the amount you work out, value is a percentage when kind is "percent", rupees when it is "flat", or answer 4xx if your order does not qualify. No offer, no discount.
Before your endpoint is called for the assistant, two checks happen in Corva, both on by default (Settings → Payments): the customer proves who they are with a code, and a person on the team approves the request. So a request may reach you some minutes after the customer asked, with requestedBy naming who approved it.
{
"requestId": "cpr_5c1f9a0b3d2e4f6a7b8c9d0e",
"orderReference": "AH-7K3QX9",
"customer": {
"id": "c1d2…",
"name": "Riya Sharma",
"phone": "+91 98765 43210"
},
"conversationId": "3e4f…",
"requestedBy": "Ava (AI), approved by Kavya Rao",
"notify": true,
"offer": {
"code": "FIRST20",
"title": "20% off your first order",
"kind": "percent",
"value": 20,
"maxDiscountRupees": 200,
"minOrderRupees": 500
}
}Then, whenever it changes, above all when your payment provider says it is paid, send its state to POST /api/v1/payments. Corva shows it on the customer's record and in the conversation, thanks the customer on WhatsApp, and the assistant can answer "has my payment gone through?".
The checklist before real customers use it.
/health answers ok from production./config rather than hard-coding the assistant's name, fields or booking word.sessionId; a new chat gets a new one; visitorId goes on every call.409 or 422 from confirm is shown, not swallowed./chat/end, and a 409 on the next message starts a new sessionId.GET /chat so a person taking over is seen, and shows their name.features.voice (and voiceSecure on https) is true.sessionId is a new chat on the business's plan (120 may start an hour). Make session ids with a cryptographic random source.id does nothing twice.Corva API v1 · additions are backwards-compatible within v1: expect new fields and new event types, and ignore what you do not know. Questions: ask whoever at the business gave you the key, or Corva support.
Orders, deliveries, bills, payments and stock for the same business: place an order, move it along, bill it, ask for payment, read stock.
Base URL https://orbo.tiruvi.site/api/v1, a separate host from the rest of this reference. Every request carries Authorization: Bearer ok_live_…, a key for the business's orders and bills, made in the Developers page of the console. Money is in paise (₹1 = 100 paise) unless a field says rupees. Times are ISO 8601. Errors are { "error": { "message": … } }.
The same description for tools: https://orbo.tiruvi.site/api/v1/openapi.json.
/ordersIdempotency-Keyheader8–100 letters, digits, dashes or underscores. A retried request with the same key returns the first answer instead of doing the thing twice. Kept a day.
referencestringThe brand's own reference, if it gave the customer one. 2–40 letters, digits, dashes.
customerobjectrequireditemsarraystorestringThe store's code. Without one, the nearest store to the address.
stagestringaddressstringlatnumberlngnumbernotesstringexpressbooleanA driver straight to the customer, ahead of the other stops. Refused at a store that does not offer it.
pickupobjectdeliveryobjectexternalRefstringsourcestring201The order, with its tracking link.
400Something in the order cannot be accepted (an unknown SKU, a store that does not offer express…).
401No key, or the key is not valid or was revoked.
403The service this call belongs to is switched off for the business.
409An order with that reference exists already.
/ordersopenquery1 for orders not yet delivered or cancelled.
phasequeryOne phase.
qqueryMatches the reference, the customer's name or phone.
limitquery1–200, default 50.
includequerystops adds each order's pickup and delivery stops.
trackingqueryThe code at the end of a customer's tracking link, to find that one order.
200OK
401No key, or the key is not valid or was revoked.
403The service this call belongs to is switched off for the business.
/orders/{reference}referencepathThe order's reference, like TD-4821.
200OK
401No key, or the key is not valid or was revoked.
403The service this call belongs to is switched off for the business.
404No such order.
/orders/{reference}referencepathThe order's reference.
stagestringrequiredA stage key from GET /stages.
notestring200OK
400No stage with that key.
401No key, or the key is not valid or was revoked.
403The service this call belongs to is switched off for the business.
404No such order.
/stagesEach stage has a phase, which is what Orbo acts on: a pickup stage opens a pickup stop, a delivery stage opens a delivery stop.
200OK
401No key, or the key is not valid or was revoked.
403The service this call belongs to is switched off for the business.
/billsIdempotency-Keyheader8–100 letters, digits, dashes or underscores. A retried request with the same key returns the first answer instead of doing the thing twice. Kept a day.
orderReferencestringrequiredcustomerobjectnotesstringdiscountCodestringOne of the business's published discounts (GET /discounts), taken off before tax.
storestringrequiredThe store's code.
itemsarrayrequiredcustomerobjectnotesstringdiscountCodestringOne of the business's published discounts (GET /discounts), taken off before tax.
201OK
401No key, or the key is not valid or was revoked.
403The service this call belongs to is switched off for the business.
409The order already has a bill.
/billsorderqueryAn order reference.
statusqueryissued or cancelled.
qqueryNumber, order or customer.
200OK
401No key, or the key is not valid or was revoked.
403The service this call belongs to is switched off for the business.
/bills/{id}idpathThe bill's id.
200OK
401No key, or the key is not valid or was revoked.
403The service this call belongs to is switched off for the business.
404No such bill.
/paymentsWith an order and no amount, asks for what the order still owes. An open link for the same ask is reused unless reuse is false.
Idempotency-Keyheader8–100 letters, digits, dashes or underscores. A retried request with the same key returns the first answer instead of doing the thing twice. Kept a day.
orderReferencestringamountPaiseintegerdescriptionstringcustomerobjectnotifybooleanSend the link to the customer.
reuseboolean201OK
401No key, or the key is not valid or was revoked.
403The service this call belongs to is switched off for the business.
/paymentsorderReferencequeryThe order's reference.
200OK
401No key, or the key is not valid or was revoked.
403The service this call belongs to is switched off for the business.
/inventory200OK
401No key, or the key is not valid or was revoked.
403The service this call belongs to is switched off for the business.
/inventory/{id}/movementsidpathThe item's id.
kindstringrequiredOne of received, used, wasted, counted.
amountnumberrequiredcostPaiseintegernotestring201OK
401No key, or the key is not valid or was revoked.
403The service this call belongs to is switched off for the business.
/summaryOrders and their value, where open orders are, deliveries, money in and owed, stock running low, new and returning customers. Sections appear for the services that are on. For an owner's dashboard, not for doing the work.
daysquery1–90, default 7.
200OK
401Not a valid key.