Rate API โ€” documentation

SQLite rates + Postgres dynamic sections ยท passkey auth ยท per-passkey analytics

Basics

Base URL http://localhost:3000. Sab kuch JSON hai. Har response ka shape fix hai:

{ "ok": true,  "data": โ€ฆ }        // success
{ "ok": false, "error": "โ€ฆ" }     // failure
IP allowlist abhi BAND hai. src/server.js me enforcement block comment out kiya hua hai, isliye filhaal kisi bhi IP se request aa sakti hai โ€” sirf passkey wali layer chal rahi hai. Wapas on karne ke liye us block ko uncomment karo; tab sirf allowlisted IP (default 91.108.105.218, ALLOWED_IPS se badlo) aur localhost chalenge, baaki sab ko 403 Forbidden: IP not allowed milega aur wo attempt ip.blocked ke roop me log hoga.

GET /health bina passkey ke uptime check deta hai.

Authentication

Har endpoint ko passkey chahiye. Teen jagah se bhej sakte ho โ€” jo suit kare. GET ke saath bhi JSON body chalti hai, to passkey / id / month sab ek hi payload me bhej sakte ho:

# 1. payload (body) me
curl -X POST http://localhost:3000/api/rates \
     -H 'content-type: application/json' \
     -d '{"passkey":"client_xxx","id":"profile_1001","rate":5820.5,"effective_from":"2026-04"}'

# 2. query string me
curl "http://localhost:3000/api/rates?passkey=client_xxx"

# 3. header me
curl -H "x-passkey: client_xxx" http://localhost:3000/api/rates
curl -H "Authorization: Bearer client_xxx" http://localhost:3000/api/rates

# GET + body -- sab kuch ek payload me
curl -X GET http://localhost:3000/api/rates \
     -H 'content-type: application/json' \
     -d '{"passkey":"client_xxx","id":"profile_1001","month":"2026-02"}'
Sirf stored passkey chalti hai. Jo passkey passkeys table me nahi hai use turant reject kiya jaata hai โ€” koi guest ya default access nahi. Reject bhi analytics me record hota hai.
SituationResponse
Passkey bheji hi nahi401 passkey missing
Passkey table me nahi hai401 Invalid passkey
Passkey hai par active = 0403 Passkey is disabled
Admin endpoint, normal key se403 Admin passkey required

Key ka scope โ€” kis key ko kitne rates dikhte hain

Har passkey ka ek scope hota hai. Ye tay karta hai ki wo key kaunse rates dekh / badal sakti hai. GET /api/me se apna scope check kar sakte ho.

scopeKaise banti haiKya dikhta hai
all Console / admin se โ€” POST /api/keys Saare rates, chahe kisi ne bhi banaye ho. Purani sab keys yahi hain.
own User khud โ€” POST /api/keys/register Sirf apne profile id ke rates โ€” read-only. Key ki id hi profile id hai, to us profile ke saare rates aur poori history dikhti hai (panel ke banaye hue bhi). Doosre profile par 404, aur kuch bhi likhne par 403.
Key ki id = profile id. own key ka filter rates.id == key.id par lagta hai โ€” "kisne banaya" par nahi. Isliye profile_1001 ki key ko profile_1001 ke wo rates bhi dikhte hain jo panel ne banaye the.

rates.owner_id ab sirf audit ke liye hai (kis key ne row banaya) โ€” usse visibility tay nahi hoti.

Profile ids

id hamesha caller deta hai. Ye bahar ke system ka profile id hai, isliye kahin bhi auto-generate nahi hota โ€” na API se, na dashboard page se. Rate create / get / update aur passkey create, sab me id tum bhejte ho.
  • Type TEXT โ€” profile_1001 jaisa string bhi, 90210 jaisa number bhi (andar string ban jaata hai)
  • Max 120 characters, trim ho jaata hai
  • / aur control characters allowed nahi โ†’ 400 Invalid id
  • Rate aur passkey dono me wahi profile id use kar sakte ho โ€” dono tables independent hain

id teen jagah se accept hota hai: URL path, ?id= query, ya payload. Teeno ek hi kaam karte hain:

GET  /api/rates/profile_1001?month=2026-03
GET  /api/rates?id=profile_1001&month=2026-03
PUT  /api/rates            {"id":"profile_1001","month":"2026-03","rate":5999.99}

Ek profile ke kai rate rows hote hain โ€” har period ka apna. Isliye har period ka apna rate_id hota hai jo server banata hai aur create ke response me wapas aata hai. Usse kisi ek period ko exactly target karte ho โ€” rate badalna ho ya month range, dono ke liye wahi id.

Error codes

CodeKab
400id / rate / effective_from missing ya invalid, kharab JSON
401Passkey missing ya table me nahi
403Passkey disabled, admin chahiye, ya IP allowlist me nahi (jab allowlist on ho)
404Us month me koi rate lagu nahi, passkey exist nahi karta, ya route galat
409Period overlap kar raha hai, ya wahi passkey id pehle se hai
413Payload 1 MB se bada

Apni key khud banao signup secret

User ko admin ke paas jaane ki zarurat nahi โ€” do call me apni key mil jaati hai. user_id hi uska profile id hai, aur aise bani key ka scope own hota hai โ€” wo sirf apne profile ka rate aur history padh sakta hai.

Pehle us profile ka rate hona zaroori hai. Jis user_id ka ek bhi rate humare paas nahi hai, uski key banti hi nahi โ€” 404 โ€ฆ ka koi rate humare paas nahi hai. Yaani rate pehle panel se banega, key baad me.
Dono endpoints ko SIGNUP_SECRET chahiye (server ke .env me). Passkey nahi โ€” signup secret. Bhejo header x-signup-secret, body signup_secret, ya query ?signup_secret= me. Server par secret set nahi hai to dono 503 dete hain (default band). Admin passkey ho to secret ki zarurat nahi.

Step 1 ยท GET /api/keys/lookup?user_id=:user_id

Pehle ye check karo ki us user ki key pehle se hai ya nahi. Passkey ki value yahan kabhi wapas nahi aati โ€” sirf "hai ya nahi" pata chalta hai.

curl "http://localhost:3000/api/keys/lookup?user_id=acme"      -H "x-signup-secret: $SIGNUP_SECRET"

# key nahi hai, par profile bhi nahi hai -> register nahi hoga
{ "ok": true, "data": { "user_id": "acme", "exists": false,
                        "profile_exists": false, "can_register": false } }

# key nahi hai aur profile maujood hai -> Step 2 par jao
{ "ok": true, "data": { "user_id": "acme", "exists": false,
                        "profile_exists": true, "can_register": true } }

# key pehle se hai -> naya register mat karo
{ "ok": true, "data": {
    "user_id": "acme", "exists": true, "profile_exists": true, "can_register": false,
    "active": true, "label": "Acme Ltd", "scope": "own", "is_admin": false,
    "created_at": "2026-09-09 11:45:53" } }
FieldMatlab
existsUs user_id ki passkey pehle se hai ya nahi
profile_existsUs profile id ka koi rate humare paas hai ya nahi
can_registerDono milakar โ€” Step 2 chalega ya nahi

Step 2 ยท POST /api/keys/register

Sirf tab jab Step 1 me can_register: true aaya ho.

FieldZaroori?Matlab
user_idhaanUser ka apna id โ€” yahi passkeys.id banta hai
labelnahiDisplay naam. Na do to user_id hi lag jaata hai
new_passkeynahiApni pasand ki passkey. Na do to server strong random banata hai
curl -X POST http://localhost:3000/api/keys/register      -H "x-signup-secret: $SIGNUP_SECRET"      -H 'content-type: application/json'      -d '{"user_id":"acme","label":"Acme Ltd"}'

{ "ok": true,
  "data": { "user_id": "acme",
            "passkey": "pk_eaad439def25eccc9cfdcfe120d958e9",
            "label": "Acme Ltd",
            "scope": "own" },
  "note": "Ye passkey ab dobara nahi dikhega -- abhi save kar lo." }
Generated passkey sirf isi ek response me milta hai. Lookup se dobara nahi nikalta. Kho gaya to admin hi GET /api/keys se dekh sakta hai ya nayi key issue karega.

Apni pasand ki passkey bhejni ho

Server ki generated key yaad rakhne ka jhanjhat na ho to new_passkey me apni value bhej do โ€” wahi store hoti hai aur wahi response me echo hoti hai:

curl -X POST http://localhost:3000/api/keys/register      -H "x-signup-secret: $SIGNUP_SECRET"      -H 'content-type: application/json'      -d '{"user_id":"acme","new_passkey":"AcmeSecret2026!x"}'

{ "ok": true,
  "data": { "user_id":"acme", "passkey":"AcmeSecret2026!x", "label":"acme", "scope":"own" },
  "note": "Ye wahi passkey hai jo tumne bheji thi." }
RuleKyun
6โ€“200 charactersYe seedha login credential hai โ€” utni hi lambi rakho jitni sambhal sako
Space / control character nahiPasskey header aur query string me bhi jaati hai, wahan ye toot jaate hain
Pehle se kisi aur ki na hoTakrayi to 409 โ€” doosri chuno
Yahi new_passkey field aur yahi rules admin wale POST /api/keys par bhi lagte hain. Analytics log me ye value hamesha masked jaati hai (Acme...26!x).

Step 3 ยท us key se apna rate padho

Normal rate API wahi hai โ€” bas x-passkey me apni key lagao. List me sirf tumhare profile ke rows aayenge, aur likhne wale endpoints band rahenge.

KEY=pk_eaad439def25eccc9cfdcfe120d958e9

# apne profile ke saare rates
curl http://localhost:3000/api/rates -H "x-passkey: $KEY"

# is month ka rate
curl "http://localhost:3000/api/rates/profile_1001?month=2026-06" -H "x-passkey: $KEY"

# poori history -- saare periods
curl http://localhost:3000/api/rates/profile_1001/history -H "x-passkey: $KEY"

# doosre profile par -> 404
curl http://localhost:3000/api/rates/profile_2002 -H "x-passkey: $KEY"
{ "ok": false, "error": "profile_2002 ka koi rate nahi hai" }

# kuch bhi badalne ki koshish -> 403
curl -X PATCH http://localhost:3000/api/rates/profile_1001 -H "x-passkey: $KEY" -d '{"rate":1}'
{ "ok": false, "error": "Ye key sirf padh sakti hai -- rate banane/badalne ke liye panel wali key chahiye" }

# apna scope check karo
curl http://localhost:3000/api/me -H "x-passkey: $KEY"
{ "ok": true, "data": { "id": "profile_1001", "label": "Acme Ltd", "is_admin": false,
                        "scope": "own", "sees_all_rates": false, "can_edit_rates": false } }
SituationResponse
Signup secret galat / nahi bheja403 Invalid or missing signup secret
Server par SIGNUP_SECRET set hi nahi503 Self-service signup band hai
Us user_id ki key pehle se hai409 โ€ฆ pehle se hai โ€” lookup se check karo
Us profile id ka koi rate hi nahi hai404 โ€ฆ ka koi rate humare paas nahi hai
Doosre profile par hit kiya404 (403 nahi โ€” doosre ka data exist hi nahi karta)
own key se create / update / delete403 Ye key sirf padh sakti hai

Rates koi bhi valid passkey ยท scope ke hisaab se

Neeche ka har endpoint key ke scope se filter hota hai: all key ko saare rates aur poora likhne ka haq, own key ko sirf apne profile id ke rates โ€” read-only. all key ?profile_id=profile_1001 laga kar ek profile ke rates alag se dekh sakti hai.
Period month-wise hai, date-wise nahi. Har rate row me effective_from (kis month se โ€” required) aur effective_to (kis month tak โ€” khaali chhodo to open-ended) hota hai, dono YYYY-MM format me. Ek profile ke kai period ho sakte hain โ€” us profile ki rate history. Bas periods aapas me overlap nahi karne chahiye.

Ek profile ki history kaise banti hai

# January me: Jan se March tak ka rate
curl -X POST http://localhost:3000/api/rates \
     -H 'content-type: application/json' \
     -d '{"passkey":"client_xxx","id":"profile_1001","name":"gold_22k","rate":5500,
          "effective_from":"2026-01","effective_to":"2026-03"}'

# April me: April se December tak ka naya rate (purana waise ka waisa rehta hai)
curl -X POST http://localhost:3000/api/rates \
     -H 'content-type: application/json' \
     -d '{"passkey":"client_xxx","id":"profile_1001","name":"gold_22k","rate":5820.5,
          "effective_from":"2026-04","effective_to":"2026-12"}'

Ab profile_1001 ke do period hain, aur har ek ka apna rate_id:

rate_id 1    2026-01 โ†’ 2026-03    rate 5500
rate_id 2    2026-04 โ†’ 2026-12    rate 5820.5
rate_id server banata hai aur create ke response me wapas deta hai. Use apne system me store kar lo โ€” baad me us period ko edit ya delete karne ka wahi sabse pakka tareeka hai (rate badalna ho ya month range, dono ke liye wahi id).

GET /api/rates/:id?month=YYYY-MM โ€” us month ka rate

Yahi main lookup hai: month bhejo, us month me jo rate lagu tha wo milega. month na do to is month ka rate.

curl "http://localhost:3000/api/rates/profile_1001?passkey=client_xxx&month=2026-03"
# -> rate 5500   (2026-01 โ†’ 2026-03 wala period)

curl "http://localhost:3000/api/rates/profile_1001?passkey=client_xxx&month=2026-06"
# -> rate 5820.5 (2026-04 โ†’ 2026-12 wala period)

{
  "ok": true,
  "data": { "rate_id": 1, "id": "profile_1001", "name": "gold_22k", "rate": 5500,
            "effective_from": "2026-01", "effective_to": "2026-03",
            "asked_month": "2026-03" }
}

month payload me bhi bhej sakte ho. Us month me koi period na mile to 404 โ€ฆ me koi rate lagu nahi hai.

GET /api/rates/:id/history โ€” saare periods

curl -H "x-passkey: client_xxx" http://localhost:3000/api/rates/profile_1001/history

GET /api/rates โ€” sab profiles

# saare periods, saare profiles
curl "http://localhost:3000/api/rates?passkey=client_xxx"

# sirf wo rates jo March me lagu the -- har profile ka ek row
curl "http://localhost:3000/api/rates?passkey=client_xxx&month=2026-03"

POST /api/rates โ€” naya period

FieldRequiredFormat
idhaanprofile id (TEXT)
ratehaannumber
effective_fromhaanYYYY-MM โ€” poori date bheji to month le lete hain
effective_tonahiYYYY-MM โ€” na do to open-ended
namenahilabel
rate_idโ€”mat bhejo โ€” server khud banata hai aur response me deta hai

Response me naye period ka rate_id aata hai โ€” usi se aage edit/delete karoge:

{
  "ok": true,
  "data": { "rate_id": 3, "id": "profile_1001", "name": "gold_22k", "rate": 6100,
            "effective_from": "2027-01", "effective_to": "2027-06",
            "created_at": "โ€ฆ", "updated_at": "โ€ฆ" }
}
Us profile ke kisi purane period se overlap hua to 409 milega, aur error me bata diya jaata hai kaunsa period takra raha hai (uske rate_id ke saath).

PUT PATCH /api/rates/:id โ€” period edit

Kaunsa period edit karna hai wo selector se chunta hai. Selector query me bhi chalega aur payload me bhi โ€” jo suit kare:

SelectorKya chunta haiKab use karo
rate_idbilkul wahi period Sabse pakka. Rate ya month range โ€” kuch bhi badalna ho, yahi use karo
monthjo period us month ko cover karta hai Jab rate_id paas na ho. Sirf rate badalne ke liye theek hai
kuch nahiis month wala periodAaj chal rahe rate ko chhedna ho

Dono selector query me bhi chalte hain aur payload me bhi โ€” ?rate_id=2 ya {"rate_id":2}, jo suit kare.

Month range badalna ho to hamesha rate_id use karo. month selector se period chunoge aur usi call me uska month range bhi badal doge, to sochna padega ki kaunsa row chuna gaya tha โ€” rate_id me ye confusion hai hi nahi.
# 1. rate_id se rate badlo -- sab payload me
curl -X PUT http://localhost:3000/api/rates \
     -H 'content-type: application/json' \
     -d '{"passkey":"client_xxx","id":"profile_1001","rate_id":1,"rate":5555}'

# 2. rate_id se month range badlo (overlap hua to 409)
curl -X PUT http://localhost:3000/api/rates \
     -H 'content-type: application/json' \
     -d '{"passkey":"client_xxx","id":"profile_1001","rate_id":1,
          "effective_from":"2025-11","effective_to":"2026-02"}'

# 3. rate_id nahi pata? month se dhundo -- Feb jis period me hai uska rate badlega
curl -X PUT http://localhost:3000/api/rates \
     -H 'content-type: application/json' \
     -d '{"passkey":"client_xxx","id":"profile_1001","month":"2026-02","rate":5999}'

# 4. period dobara open-ended
curl -X PUT "http://localhost:3000/api/rates/profile_1001?rate_id=2" \
     -H 'content-type: application/json' \
     -d '{"passkey":"client_xxx","effective_to":null}'

Jo field bhejoge sirf wahi badlega โ€” baaki waise ke waise rehte hain, aur updated_at apne aap set hota hai.

effective_to ko null kar sakte ho, par effective_from ko khaali nahi โ€” rate hamesha kisi month se lagu hota hai.

DELETE /api/rates/:id โ€” ek period hatao

Wahi selectors chalte hain (query ya payload). Sirf wo period delete hota hai, baaki history bachi rehti hai. rate_id doosre profile ka ho to 404 โ€” ek profile doosre ka period nahi chhed sakta.

curl -X DELETE "http://localhost:3000/api/rates/profile_1001?passkey=client_xxx&rate_id=1"

{ "ok": true, "deleted": { "id": "profile_1001", "rate_id": 1 } }

GET /api/me

Ye passkey kiski hai โ€” { id, label, is_admin }.

Passkeys admin only

GET /api/keys

Saari keys โ€” id, passkey, label, is_admin, scope, active, created_at. Kisi user ka kho gaya passkey yahin se dobara mil sakta hai.

POST /api/keys โ€” nayi key issue karo

Body: id (required, profile id) ยท label ยท new_passkey (optional โ€” na do to secret auto-generate; diya to 6โ€“200 chars, bina space) ยท is_admin ยท scope.

Yahan se bani key ka scope default all hai โ€” usse saare rates dikhte hain. User-jaisi restricted key chahiye to "scope": "own" bhejo (wahi jo /api/keys/register khud banata hai).
curl -X POST http://localhost:3000/api/keys \
     -H 'content-type: application/json' -H 'x-passkey: admin_xxx' \
     -d '{"id":"profile_7788","label":"partner-api"}'

{ "ok": true, "data": { "id": "profile_7788", "passkey": "pk_bf97โ€ฆ", "label": "partner-api",
                        "is_admin": 0, "scope": "all", "active": 1, "created_at": "โ€ฆ" } }

PATCH /api/keys/:id โ€” enable / disable / rename

curl -X PATCH http://localhost:3000/api/keys/profile_7788 \
     -H 'content-type: application/json' -H 'x-passkey: admin_xxx' \
     -d '{"active":false}'

Body: active ยท label ยท scope ("all" ya "own") โ€” yani kisi user ki key ko baad me full access de sakte ho ya wapas apne rates tak seemit kar sakte ho.

Disable ki hui key ki har call 403 hoti hai, par analytics me auth.disabled ke roop me record hoti rehti hai.

DELETE /api/keys/:id

Apni hi key delete nahi kar sakte. Key delete karne par uske purane analytics events bane rehte hain (passkey_id null ho jaata hai).

Analytics admin only

GET /api/analytics/summary?days=7

Dashboard jo dikhata hai wahi data:

  • totals โ€” total / ok / failed requests, active keys, avg latency, today
  • byAction โ€” kis action ke kitne hits
  • byKey โ€” per passkey: get / create / update / delete / failed counts + last seen
  • daily โ€” din-wise hits aur failures (days se range)
  • hourly โ€” pichle 24 ghante ghanta-wise
  • topRates โ€” sabse zyada request hone wale rate records

GET /api/analytics/events

Raw event log. Query: limit (max 500) ยท offset ยท passkey_id ยท action ยท failed=1.

curl -H "x-passkey: admin_xxx" \
  "http://localhost:3000/api/analytics/events?passkey_id=profile_1001&limit=50"

{ "ok": true, "data": { "total": 665, "rows": [ โ€ฆ ] } }
Log me passkey hamesha masked hoti hai (clie...0fa1), aur payload me se passkey / token jaise fields mask karke store hote hain.

Dynamic sections โ€” kaise kaam karta hai

Upar wala /api/rates ek fixed table hai โ€” uske columns code me likhe hain. Sections uska ulta hai: tum runtime par table banate ho, uske fields aur unke types tay karte ho, aur batate ho kaun si passkeys usko kaise use kar sakti hain.

Do alag database. Rates / passkeys / analytics SQLite par hai (jaisa tha waisa hi). Sections aur unka data Postgres par hai (PG_URL). Postgres band ho to /api/sections aur /api/data 503 dete hain โ€” baaki API normal chalti rehti hai.

Ek section banane par teen cheezein ek hi transaction me hoti hain:

  1. dyn_sections me section ka row
  2. dyn_fields me har field ka row โ€” yahi schema ka record hai
  3. asli sec_<slug> table, jiska DDL unhi field rows se banta hai

Yaani field definitions sirf documentation nahi hain โ€” Postgres ke asli columns, NOT NULL, UNIQUE, DEFAULT aur CHECK unse hi bante hain. Isliye validation do jagah lagti hai: pehle JS me (saaf error message ke liye), phir DB me (aakhri guarantee).

Ek section, do keys

Access (section, passkey) ka joda hai, aur har jode ke apne teen flags โ€” can_read, can_write, can_delete. Ek section ko jitni chaaho keys se jod sakte ho, aur har key ko alag permission de sakte ho:

invoices  โ†  profile_1001   read โœ“  write โœ“  delete โœ—
invoices  โ†  profile_1002   read โœ“  write โœ—  delete โœ—
branches  โ†  profile_1001   read โœ“  write โœ“  delete โœ“

admin passkey  โ†’  har section par sab kuch, hamesha
Passkeys SQLite me hain aur grants Postgres me โ€” do alag database, isliye beech me foreign key nahi hai. Practical matlab: koi key delete karo to uska grant row Postgres me pada reh jaata hai. Wo bekaar hai (key hi nahi bachi, auth pehle hi fail hoga), par chahe to DELETE /api/sections/:slug/access/:passkey_id se saaf kar do.

Field types

Live list GET /api/types se milti hai (dashboard ka dropdown aur ye table wahi se bante hain).

typePostgres columnKya accept karta hai
textTEXTKoi bhi string. options.max_length se limit
numberDOUBLE PRECISIONDecimal number โ€” 5820.5
intBIGINTPoora number. 2.7 reject hoga
boolBOOLEANtrue, 1, "yes", "on" โ€” sab chalega
dateDATEYYYY-MM-DD
datetimeTIMESTAMPTZISO timestamp
jsonJSONBNested object / array
uuidUUIDStandard uuid
enumTEXT + CHECKoptions.choices me allowed values โ€” DB level par bandha hua

Har field ke saath ye flags de sakte ho:

FlagMatlab
requiredNOT NULL. Rows pehle se hon to default bhi dena padega
uniqueUNIQUE constraint. Repeat value par 409
indexedUs column par index โ€” filter tez. unique ke saath zaroorat nahi
defaultColumn ka DEFAULT. Field ke type se validate hota hai
labelDashboard me dikhne wala naam. DB pe asar nahi
Naam ke rules. slug: lowercase letter se shuru, sirf a-z 0-9 _, max 32 chars. Field name: wahi rules, max 48 chars. record_id, created_by, created_at, updated_at reserved hain โ€” ye columns server khud banata hai.

Section builder admin only

POST /api/sections โ€” naya section

Fields aur access grants ek hi call me de sakte ho:

curl -X POST http://localhost:3000/api/sections \
  -H "x-passkey: $ADMIN" -H "content-type: application/json" -d '{
  "slug": "invoices",
  "label": "Client invoices",
  "description": "Har invoice ek row",
  "fields": [
    { "name": "invoice_no",  "type": "text",   "required": true, "unique": true },
    { "name": "client_name", "type": "text",   "indexed": true },
    { "name": "amount",      "type": "number", "required": true },
    { "name": "status",      "type": "enum",   "options": { "choices": ["draft","sent","paid"] },
                                               "default": "draft" },
    { "name": "due_date",    "type": "date" },
    { "name": "paid",        "type": "bool",   "default": "false" },
    { "name": "line_items",  "type": "json" }
  ],
  "access": [
    { "passkey_id": "profile_1001", "can_read": true, "can_write": true },
    { "passkey_id": "profile_1002", "can_read": true }
  ]
}'

Peeche Postgres me ye chalta hai:

CREATE TABLE sec_invoices (
  record_id    BIGSERIAL PRIMARY KEY,
  invoice_no   TEXT   NOT NULL,
  client_name  TEXT,
  amount       DOUBLE PRECISION NOT NULL,
  status       TEXT   DEFAULT 'draft' CONSTRAINT ck_sec_invoices_p3
                        CHECK (status IN ('draft','sent','paid')),
  due_date     DATE,
  paid         BOOLEAN DEFAULT FALSE,
  line_items   JSONB,
  created_by   TEXT,
  created_at   TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at   TIMESTAMPTZ NOT NULL DEFAULT now()
);
ALTER TABLE sec_invoices ADD CONSTRAINT uq_sec_invoices_p0 UNIQUE (invoice_no);
CREATE INDEX ix_sec_invoices_p1 ON sec_invoices (client_name);

GET /api/sections โ€” sab sections

Har section ke saath uske fields, access grants aur record count.

GET /api/sections/:slug

PATCH /api/sections/:slug โ€” label / description

DELETE /api/sections/:slug?confirm=:slug

Ye DROP TABLE karta hai โ€” section ka saara data chala jaata hai. Isliye confirm me slug dobara bhejna padta hai, warna 400.

POST /api/sections/:slug/fields โ€” naya field

curl -X POST .../api/sections/invoices/fields \
  -H "x-passkey: $ADMIN" -H "content-type: application/json" \
  -d '{ "name": "notes", "type": "text", "label": "Internal notes" }'

ALTER TABLE โ€ฆ ADD COLUMN chalta hai, aur field turant usable ho jaata hai.

Section me rows pehle se hain aur naya field required hai to default dena zaroori hai โ€” warna purani rows ka wo column khaali reh jaata aur NOT NULL lag hi nahi sakta. Bina default bhejne par saaf 400 milta hai.

PATCH /api/sections/:slug/fields/:name

label, required, unique, indexed, default aur options (enum ke choices) badal sakte ho. Type badalna support nahi hai โ€” uske liye field delete karke naye type se banao.

DELETE /api/sections/:slug/fields/:name

DROP COLUMN โ€” us column ka data chala jaata hai. Aakhri field delete nahi ho sakta.

Access grants admin only

GET /api/sections/:slug/access

PUT /api/sections/:slug/access โ€” grant set / update

Upsert hai โ€” key pehle se ho to uske flags badal jaate hain, warna naya grant ban jaata hai.

curl -X PUT .../api/sections/invoices/access \
  -H "x-passkey: $ADMIN" -H "content-type: application/json" -d '{
  "access": [
    { "passkey_id": "profile_1001", "can_read": true, "can_write": true, "can_delete": true },
    { "passkey_id": "profile_1003", "can_read": true }
  ]
}'

Koi flag na do to sirf can_read โ€” default hamesha sabse kam permission.

DELETE /api/sections/:slug/access/:passkey_id

GET /api/sections/status

Postgres zinda hai ya nahi, kaun sa database, kitne sections/fields/grants.

Section ka data jis key ko access ho

Ye endpoints admin-only nahi hain. Koi bhi valid passkey chalti hai โ€” bas us section par uska grant hona chahiye. Grant na ho to 403, permission na ho to bhi 403.

GET /api/data/:slug/schema โ€” pehle yahi dekho

Client ko bataata hai ki is section me kaun se fields hain, kis type ke, kya required hai, aur meri key kya kar sakti hai. Isse client bina docs padhe form bana sakta hai.

curl -H "x-passkey: $CLIENT" .../api/data/invoices/schema

GET /api/data/:slug โ€” list

Response top level par hai (data me lipta hua nahi):

{ "ok": true, "section": "invoices", "total": 42, "limit": 50, "offset": 0,
  "rows": [ { "record_id": 1, "invoice_no": "INV-001", ... } ] }

Filters query string me โ€” field ka naam + optional operator suffix:

QueryMatlab
?status=paidbarabar
?status__ne=draftbarabar nahi
?amount__gt= __gte= __lt= __lte=bada / chhota
?client_name__like=acmecase-insensitive match (% khud lag jaata hai)
?status__in=sent,paidin me se koi ek
?due_date__null=1khaali hai (=0 se ulta)
?q=acmesaare text/enum/uuid fields me search
?sort=amount&order=descsort โ€” default record_id desc
?limit=50&offset=0paging โ€” max limit 500

Anjaan field par filter lagane par 404 milta hai โ€” chup-chaap ignore nahi hota.

POST /api/data/:slug โ€” naya record

curl -X POST .../api/data/invoices \
  -H "x-passkey: $CLIENT" -H "content-type: application/json" -d '{
  "invoice_no": "INV-2026-001",
  "client_name": "Acme Corp",
  "amount": 48500.75,
  "status": "sent",
  "due_date": "2026-09-15",
  "line_items": [ { "desc": "design", "qty": 2 } ]
}'

created_by me calling passkey ki id apne aap chali jaati hai. Fields ko {"data": { โ€ฆ }} me lapet ke bhi bhej sakte ho โ€” dono chalega.

Typo chup-chaap nahi girta. Jo key section ka field nahi hai (jaise amont) uspe 400 milta hai, taki tumhe na lage ki value save ho gayi.

GET /api/data/:slug/:record_id

PATCH /api/data/:slug/:record_id โ€” partial update

Jo fields bhejo wahi badalte hain, baaki waise hi rehte hain. Optional field ko null bhej ke khaali kar sakte ho; required field null par 400.

DELETE /api/data/:slug/:record_id

can_delete chahiye.

GET /api/my-sections

Meri key kaun se sections use kar sakti hai, kis permission ke saath. Admin ko sab dikhte hain.

GET /api/types

Saare field types ki live list.

Analytics actions

Har event ka action in me se ek hota hai:

ActionMatlab
rate.listPoori list maangi
rate.getKisi month ka rate padha
rate.historyEk profile ke saare periods maange
rate.createNaya period bana
rate.updatePeriod ka rate ya range badli
rate.deleteEk period delete hua
auth.missingPasskey bheji hi nahi
auth.invalidPasskey table me nahi hai
auth.disabledKey disabled hai
auth.forbiddenAdmin endpoint, normal key se
ip.blockedIP allowlist me nahi (allowlist on hone par hi aata hai)
route.missingAisa koi endpoint nahi
key.create / key.update / key.deleteAdmin ne keys chhedin
key.listAdmin ne saari keys maangi โ€” sirf fail hone par log hota hai
auth.me/api/me hit hua โ€” sirf fail hone par log hota hai
rate.readonlyown key ne rate badalne ki koshish ki
key.lookupKisi ne check kiya ki us user ki key hai ya nahi
key.registerUser ne khud self-service key banayi
signup.disabledSIGNUP_SECRET server par set hi nahi hai
signup.forbiddenSignup secret galat ya bheja hi nahi

Dynamic sections ke actions

ActionMatlab
section.create / section.update / section.deleteSection bana / badla / gaya
section.list / section.getSchema padha gaya
field.create / field.update / field.deleteField add / patch / drop hua
access.set / access.revoke / access.listGrants chhede gaye
data.list / data.getSection ka data padha
data.create / data.update / data.deleteRecord bana / badla / gaya
data.schemaClient ne section ka schema maanga
dyn.forbiddenKey valid thi par us section pe permission nahi
dyn.unconfiguredPG_URL set hi nahi hai
dyn.unavailablePostgres se connect nahi hua
dyn.types / dyn.status / dyn.mineField types, PG health, meri sections list
Section ke hits me target_id section ka slug hota hai (rates me profile id hota hai). Isliye dashboard ke "Top profiles" table me dono mil ke dikh sakte hain.

Tables

rates                              -- ek profile ke kai rows (rate history)
  rate_id         INTEGER PRIMARY KEY  -- har period ka apna id, server banata hai
  id              TEXT              -- profile id, caller deta hai (repeat hota hai)
  owner_id        TEXT  -> passkeys.id  -- AUDIT: kis key ne banaya (visibility isse tay nahi hoti)
  name            TEXT
  rate            REAL
  effective_from  TEXT              -- YYYY-MM, is month se lagu
  effective_to    TEXT              -- YYYY-MM, is month tak (NULL = open-ended)
  created_at      TEXT
  updated_at      TEXT

passkeys
  id          TEXT PRIMARY KEY     -- profile id, caller deta hai
  passkey     TEXT UNIQUE          -- secret
  label       TEXT
  is_admin    INTEGER
  scope       TEXT                 -- 'all' = saare rates + write | 'own' = sirf apni id ke rates, read-only
  active      INTEGER
  created_at  TEXT

analytics
  id          INTEGER PRIMARY KEY  -- log row counter
  passkey_id  TEXT  -> passkeys.id
  passkey     TEXT                 -- masked
  label       TEXT
  action      TEXT
  method      TEXT
  path        TEXT
  target_id   TEXT                 -- kis rate pe
  status      INTEGER
  ok          INTEGER
  duration_ms INTEGER
  ip          TEXT
  user_agent  TEXT
  payload     TEXT                 -- secrets masked
  error       TEXT
  created_at  TEXT

Postgres โ€” dynamic sections (alag database)

dyn_sections                       -- ek row = ek section
  section_id   BIGSERIAL PRIMARY KEY
  slug         TEXT UNIQUE       -- api me yahi dikhta hai
  table_name   TEXT UNIQUE       -- sec_<slug>
  label        TEXT
  description  TEXT
  created_by   TEXT              -- kis passkey ne banaya
  created_at   TIMESTAMPTZ
  updated_at   TIMESTAMPTZ

dyn_fields                         -- section ka schema; DDL isi se banta hai
  field_id      BIGSERIAL PRIMARY KEY
  section_id    BIGINT -> dyn_sections.section_id  (ON DELETE CASCADE)
  name          TEXT              -- asli column ka naam
  label         TEXT
  type          TEXT              -- text | number | int | bool | date |
                                  -- datetime | json | uuid | enum
  required      BOOLEAN           -- NOT NULL
  is_unique     BOOLEAN           -- UNIQUE constraint
  indexed       BOOLEAN           -- index
  default_value TEXT              -- DEFAULT (type se validate hota hai)
  options       JSONB             -- enum choices, text max_length
  position      INTEGER           -- constraint naam isi se bante hain, reuse nahi hote
  UNIQUE (section_id, name)

dyn_access                         -- kaun si key, kaunsa section, kya kar sakti hai
  section_id  BIGINT -> dyn_sections.section_id  (ON DELETE CASCADE)
  passkey_id  TEXT              -- SQLite ki passkeys.id -- doosra db, isliye FK nahi
  can_read    BOOLEAN
  can_write   BOOLEAN
  can_delete  BOOLEAN
  granted_by  TEXT
  PRIMARY KEY (section_id, passkey_id)   -- ek section, kitni bhi keys

sec_<slug>                          -- har section ki apni table
  record_id   BIGSERIAL PRIMARY KEY
  <tumhare fields>                 -- dyn_fields se generate
  created_by  TEXT              -- kis passkey ne banaya
  created_at  TIMESTAMPTZ
  updated_at  TIMESTAMPTZ