Events, files and verification
Event stream
GET /v1/events is a server-sent event stream. One connection carries every channel the caller asked for and may read. GET /v1/events/channels lists those channels, and pollEvents is a long-polling fallback for networks that break streams.
- Each event's ID is
<block>.<txIndex>.<n>, so IDs are ordered and identical on every API server. - The backend keeps 24 hours of events per channel. A client that reconnects with
Last-Event-IDreceives what it missed and then the live stream. After a longer gap, or a409, it fetches its lists again. - Events carry IDs, numbers, statuses, versions and ledger references only, never names or narratives. The client fetches details over REST.
- Permission is checked when a client subscribes and again whenever the person's key, posting, delegation or the court-station map changes. A channel the person loses is closed with
channel.revoked.
| Channel | Carries |
|---|---|
me | The person's notifications, action.committed and action.failed for their own actions, document.ready, key.changed, session.ended |
citizen.cases | case.created, case.updated and case.status_changed for a citizen's complaints; never hints or referral details |
case:<caseId> | Every change to a case: diary, exhibits, reports, orders, status requests, referrals |
station:<code>, station:<code>.desk, station:<code>.gd | A station's cases and exhibits, the front desk's queue, the general diary |
io:<officialId> | An IO's assigned cases, deadlines and laboratory notices |
court:<courtCode> | Referrals, final reports, status replies and orders for a court |
lab:<labId>:<division> | Custody offers, parcel arrivals and laboratory updates |
exhibit:<exhibitId> | An exhibit's updates and custody offers |
explorer.blocks | Block numbers and transaction counts, for the console |
system | Maintenance notices and directory changes |
Push notifications through FCM only wake the app. FCM is the one Firebase service DigiFIR still uses.
Uploading a file
Files are encrypted on the device before upload, and the backend never holds their keys.
- Encrypt the file with AES-256-GCM using a new random 256-bit key and 96-bit nonce. Compute the SHA-256 of the plain file and of the ciphertext.
- Call
POST /v1/fileswithcipherSha256,size,plainSha256,plainSize,mediaType,purposeand the case or exhibit it belongs to. The file's ID is its ciphertext hash, so the same upload twice is harmless. - Up to 8 MiB, upload with one presigned
PUT. Above 8 MiB, upload in 8 MiB parts;POST /v1/files/{id}/partspresigns more parts andGET /v1/files/{id}lists the parts already stored, so an upload can resume. - Call
POST /v1/files/{id}/completion. The backend checks the size and recomputes the SHA-256; a mismatch deletes the object. - Name the file's hashes in the action that uses it, and put its key in the action's private payload.
| Limit | Value |
|---|---|
| A file | Up to 50 MiB of plaintext |
| Evidence media (seizure videos, statement recordings) | Up to 2 GiB |
| Files | 10 per action, 50 per case |
| Total ciphertext per case | 20 GiB by default |
| Pending uploads per person | 6 GiB by default |
| Unreferenced uploads | Deleted after 24 hours |
NOTE
The SDKs read a large file twice in chunks, once to hash it and once to encrypt and upload it, so a 2 GiB video never has to fit in the phone's memory.
Downloading a file
GET /v1/files/{id}/content is allowed to anyone who may read a case or exhibit that references the file. It answers with a presigned URL valid for 5 minutes, and is audited. The client decrypts with the key from a signed private read. After a governed erasure has purged every copy of the key, the answer is 410 Gone.
Documents
The backend renders documents as deterministic PDF/A-2b files with embedded Indic fonts (Noto Sans in Devanagari, Bengali, Gurmukhi, Gujarati, Tamil, Telugu, Kannada and Malayalam) and a verification QR code. It stores them encrypted and registers their SHA-256 on the ledger.
| Document | Rendered from | Delivered to |
|---|---|---|
| Complaint receipt | The case and the complaint | The citizen app and portal, or an SMS link for a desk informant |
| FIR copy (BNSS s.173(2)), Zero FIR copy, NCR copy | The case | The informant by statutory SMS link, the app, and DigiLocker |
| Final report, arrest memo, seizure memo | Uploaded PDF, or the case's records | Court and police |
| Court order | The judicial action and its reasons, or an uploaded signed PDF | Police and citizen |
| Laboratory report PDF | The sealed report | IO, court |
| NCR fortnightly report (s.174(1)(ii)) | The station's general diary | The SHO, to send to the magistrate |
eSign
The BNSS s.173(1)(ii) signature on an electronic complaint, and some certificates such as the BSA s.63 certificate, use Aadhaar OTP eSign through a CCA-licensed eSign service provider.
POST /v1/esign/sessionsprepares the request and returns what will be signed, with a line the signer can compare: the complaint number and the first 16 hex digits of the statement's commitment.- The app opens the provider's page in the phone's browser through
GET /v1/esign/sessions/{id}/launch. The Aadhaar number is typed only on the provider's page. - The provider's signed response arrives at
/v1/webhooks/esign/{provider}, and the backend checks it against the provider's certificate. - For a complaint, the response is accepted only if the signer's name and Aadhaar last four digits match the identity verified for the complainant. The state's eSign adapter then records
complaint.signon the ledger.
Until a provider is approved, sessions go to the built-in eSign simulator.
Public verification
| Endpoint | Purpose |
|---|---|
POST /v1/verify | Check a document by its SHA-256, computed on the device. No sign-in needed. |
POST /v1/verify/official | Check an official's ID shown on their phone |
POST /v1/verify answers exists, and when the document is found: its kind, number, station or laboratory, issue day, the case's current status, the kind of issuer, and the ledger transaction and block. Audit trail and verification explains the rules.
An official's ID in the police and court apps is a QR code that the device key signs again every minute. When a citizen scans it, the backend checks the signature, that the key is active and belongs to the named official, that the code is less than 120 seconds old, and that the roster shows the official active or on leave. A valid answer carries only what an ID card shows: name, rank, designation and station. Each code passes once, so a photograph of it is useless to anyone else.
Sources: digifir-backend docs/architecture/BACKEND.md sections 7, 9 and 11.4, openapi/digifir.yaml.