urmi-ai/frontend/src/lib/api.ts
2026-10-02 12:51:46 +05:30

825 lines
24 KiB
TypeScript

import { API_BASE_URL } from './apiBase'
export type DashboardResponse = {
metrics: {
voice_calls: number
messages_sent: number
delivery_rate: number
api_requests: number
balance: number
}
activity: Array<{
id: string
type: string
title: string
detail: string
status: string
occurred_at: string
}>
}
export async function getDashboard(signal?: AbortSignal): Promise<DashboardResponse> {
const response = await fetch(`${API_BASE_URL}/api/v1/dashboard`, {
signal,
credentials: 'include',
})
if (!response.ok) throw new Error(`Dashboard request failed: ${response.status}`)
return response.json() as Promise<DashboardResponse>
}
export type AuthUser = {
id: string
email: string
name: string
avatar_url: string | null
email_verified: boolean
has_password: boolean
providers: string[]
}
async function authRequest(path: string, init?: RequestInit): Promise<AuthUser> {
const response = await fetch(`${API_BASE_URL}/api/v1/auth${path}`, {
...init,
credentials: 'include',
headers: { 'Content-Type': 'application/json', ...init?.headers },
})
if (!response.ok) {
const body = await response.json().catch(() => null)
throw new Error(body?.detail ?? 'Authentication request failed')
}
return response.json() as Promise<AuthUser>
}
export const authApi = {
me: () => authRequest('/me'),
login: (email: string, password: string) =>
authRequest('/login', { method: 'POST', body: JSON.stringify({ email, password }) }),
register: (name: string, email: string, password: string) =>
authRequest('/register', { method: 'POST', body: JSON.stringify({ name, email, password }) }),
setPassword: (password: string, currentPassword?: string) =>
authRequest('/password', {
method: 'POST',
body: JSON.stringify({ password, current_password: currentPassword || null }),
}),
logout: () =>
fetch(`${API_BASE_URL}/api/v1/auth/logout`, { method: 'POST', credentials: 'include' }),
googleUrl: `${API_BASE_URL}/api/v1/auth/google/start`,
}
export type KycDocument = {
id: string
document_type: string
original_filename: string | null
status: 'missing' | 'pending' | 'approved' | 'rejected'
review_note: string | null
uploaded_at: string | null
reviewed_at: string | null
}
export type KycApplication = {
id: string
pan_number: string | null
gst_number: string | null
status: string
completed: boolean
submitted_at: string | null
approved_at: string | null
documents: KycDocument[]
}
export type DltDocument = {
id: string
document_type: string
original_filename: string | null
status: 'missing' | 'pending' | 'approved' | 'rejected'
review_note: string | null
uploaded_at: string | null
reviewed_at: string | null
}
export type DltApplication = {
id: string
status: string
completed: boolean
submitted_at: string | null
approved_at: string | null
pe_tm_chain_binding_id: string
documents: DltDocument[]
}
export type ComplianceStatus = {
kyc_completed: boolean
dlt_completed: boolean
console_unlocked: boolean
}
async function jsonRequest<T>(path: string, init?: RequestInit): Promise<T> {
const response = await fetch(`${API_BASE_URL}${path}`, {
...init,
credentials: 'include',
headers: init?.body instanceof FormData ? init.headers : { 'Content-Type': 'application/json', ...init?.headers },
})
if (!response.ok) {
const body = await response.json().catch(() => null)
throw new Error(typeof body?.detail === 'string' ? body.detail : 'Request failed')
}
if (response.status === 204) return undefined as T
return response.json() as Promise<T>
}
export const kycApi = {
me: () => jsonRequest<KycApplication>('/api/v1/kyc/me'),
update: (pan_number: string, gst_number: string) =>
jsonRequest<KycApplication>('/api/v1/kyc/me', { method: 'PUT', body: JSON.stringify({ pan_number, gst_number }) }),
upload: (documentType: string, file: File) => {
const body = new FormData()
body.append('file', file)
return jsonRequest<KycApplication>(`/api/v1/kyc/documents/${documentType}`, { method: 'POST', body })
},
submit: () => jsonRequest<KycApplication>('/api/v1/kyc/submit', { method: 'POST' }),
templateUrl: `${API_BASE_URL}/api/v1/kyc/template`,
}
export const dltApi = {
me: () => jsonRequest<DltApplication>('/api/v1/dlt/me'),
loaUrl: `${API_BASE_URL}/api/v1/dlt/loa`,
upload: (documentType: string, file: File) => {
const body = new FormData()
body.append('file', file)
return jsonRequest<DltApplication>(`/api/v1/dlt/documents/${documentType}`, { method: 'POST', body })
},
confirmChainBinding: () =>
jsonRequest<DltApplication>('/api/v1/dlt/chain-binding/confirm', { method: 'POST' }),
submit: () => jsonRequest<DltApplication>('/api/v1/dlt/submit', { method: 'POST' }),
}
export const complianceApi = {
status: () => jsonRequest<ComplianceStatus>('/api/v1/compliance/status'),
}
export type AdminKycApplication = KycApplication & {
user: { id: string; name: string; email: string }
}
export type AdminDltApplication = DltApplication & {
user: { id: string; name: string; email: string }
}
export type SenderConfig = {
id: string
sender_id: string
sender_type: 'promotional' | 'transactional'
purpose: string
dlt_entity_id: string
status: 'pending' | 'approved' | 'rejected'
review_note: string | null
created_at: string
reviewed_at: string | null
}
export type AdminSenderConfig = SenderConfig & {
user: { id: string; name: string; email: string }
}
export type TemplateConfig = {
id: string
sender_config_id: string
sender_id: string
template_name: string
template_text: string
dlt_template_id: string
status: 'pending' | 'approved' | 'rejected'
review_note: string | null
created_at: string
reviewed_at: string | null
}
export const messagingApi = {
listSenders: () => jsonRequest<SenderConfig[]>('/api/v1/messaging/senders'),
createSender: (payload: {
sender_id: string
sender_type: string
purpose: string
dlt_entity_id: string
}) => jsonRequest<SenderConfig>('/api/v1/messaging/senders', { method: 'POST', body: JSON.stringify(payload) }),
listTemplates: () => jsonRequest<TemplateConfig[]>('/api/v1/messaging/templates'),
createTemplate: (payload: {
sender_config_id: string
template_name: string
template_text: string
dlt_template_id: string
}) => jsonRequest<TemplateConfig>('/api/v1/messaging/templates', { method: 'POST', body: JSON.stringify(payload) }),
send: (payload: {
sender_config_id: string
template_id: string
numbers: string
message: string
campaign_name?: string | null
}) => jsonRequest<ComposeSendResponse>('/api/v1/messaging/send', { method: 'POST', body: JSON.stringify(payload) }),
}
export type CampaignRunMode = 'manual' | 'on_upload' | 'scheduled'
export type CampaignCsvUpload = {
id: string
original_filename: string
total_rows: number
status: 'validated' | 'running' | 'completed' | 'failed'
run_started_at: string | null
run_completed_at: string | null
created_at: string
}
export type Campaign = {
id: string
name: string
sender_config_id: string
sender_id: string
sender_type: 'promotional' | 'transactional'
template_id: string
template_name: string
template_text: string
number_column: string
variable_columns: string[]
run_mode: CampaignRunMode
scheduled_at: string | null
status: string
created_at: string
updated_at: string
csv_upload_count: number
csv_uploads: CampaignCsvUpload[]
}
export const campaignsApi = {
list: () => jsonRequest<Campaign[]>('/api/v1/campaigns'),
get: (campaignId: string) => jsonRequest<Campaign>(`/api/v1/campaigns/${campaignId}`),
create: (payload: {
name: string
sender_config_id: string
template_id: string
number_column: string
variable_columns: string[]
run_mode: CampaignRunMode
scheduled_at?: string | null
}) => jsonRequest<Campaign>('/api/v1/campaigns', { method: 'POST', body: JSON.stringify(payload) }),
listCsvUploads: (campaignId: string) =>
jsonRequest<CampaignCsvUpload[]>(`/api/v1/campaigns/${campaignId}/csv`),
uploadCsv: (campaignId: string, file: File) => {
const body = new FormData()
body.append('file', file)
return jsonRequest<CampaignCsvUpload>(`/api/v1/campaigns/${campaignId}/csv`, { method: 'POST', body })
},
runAutomation: (campaignId: string) =>
jsonRequest<{ processed_uploads: number; note: string }>(`/api/v1/campaigns/${campaignId}/run`, { method: 'POST' }),
runCsv: (campaignId: string, uploadId: string) =>
jsonRequest<CampaignCsvUpload & { note?: string }>(
`/api/v1/campaigns/${campaignId}/csv/${uploadId}/run`,
{ method: 'POST' },
),
}
export type ComposeSendResultItem = {
to: string
success: boolean
job_id: string | null
message_id: string | null
error: string | null
}
export type ComposeSendResponse = {
success: boolean
campaign_name: string | null
sent: number
failed: number
sender_id: string
template_id: string
template_name: string
dlt_template_id: string
results: ComposeSendResultItem[]
}
export type ApiKey = {
id: string
name: string
key_prefix: string
created_at: string
last_used_at: string | null
revoked_at: string | null
status: 'active' | 'revoked'
}
export type ApiKeyWithToken = ApiKey & {
token: string
}
export const apiKeysApi = {
list: () => jsonRequest<ApiKey[]>('/api/v1/api-keys'),
create: (name: string) =>
jsonRequest<ApiKeyWithToken>('/api/v1/api-keys', { method: 'POST', body: JSON.stringify({ name }) }),
regenerate: (keyId: string) =>
jsonRequest<ApiKeyWithToken>(`/api/v1/api-keys/${keyId}/regenerate`, { method: 'POST' }),
revoke: (keyId: string) =>
fetch(`${API_BASE_URL}/api/v1/api-keys/${keyId}`, { method: 'DELETE', credentials: 'include' }).then((response) => {
if (!response.ok) throw new Error('Unable to revoke API key')
}),
}
export type MessageApiDoc = {
id: string
method: string
path: string
title: string
summary: string
authentication: {
type: string
header: string
format: string
alternate_header: string
}
request_body: Record<string, {
type: string
required: boolean
description: string
example?: string
}>
responses: Record<string, { description: string; example?: Record<string, unknown> }>
}
export type SendMessageResponse = {
success: boolean
job_id: string | null
message_id: string | null
to: string | null
sender_id: string
template_id: string
template_name: string
dlt_template_id: string
}
export const messagesApi = {
docs: () => jsonRequest<MessageApiDoc>('/api/v1/messages/docs/send'),
send: (apiKey: string, payload: {
sender_id: string
template_id: string
to: string
message: string
}) =>
fetch(`${API_BASE_URL}/api/v1/messages/send`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify(payload),
}).then(async (response) => {
const body = await response.json().catch(() => null)
if (!response.ok) {
throw new Error(typeof body?.detail === 'string' ? body.detail : 'SMS send request failed')
}
return body as SendMessageResponse
}),
}
export type UrmiOtpProject = {
id: string
project_name: string
message_label: string
app_type: 'web' | 'mobile'
app_url: string
purpose: string
status: 'pending' | 'approved' | 'rejected'
review_note: string | null
terms_accepted_at: string
created_at: string
reviewed_at: string | null
}
export type OtpTemplateDefinition = {
type: string
name: string
content: string
sample: string
uses: string[]
variables: string[]
}
export type UrmiOtpDocumentation = {
project: UrmiOtpProject
templates: OtpTemplateDefinition[]
api: {
method: string
path: string
implemented: boolean
summary: string
parameters: Record<string, string>
example: Record<string, unknown>
}
}
export type AdminUrmiOtpProject = UrmiOtpProject & {
user: { id: string; name: string; email: string }
}
export const urmiOtpApi = {
listProjects: () => jsonRequest<UrmiOtpProject[]>('/api/v1/urmiotp/projects'),
createProject: (payload: {
project_name: string
message_label: string
app_type: 'web' | 'mobile'
app_url: string
purpose: string
terms_accepted: boolean
}) => jsonRequest<UrmiOtpProject>('/api/v1/urmiotp/projects', {
method: 'POST',
body: JSON.stringify(payload),
}),
documentation: (projectId: string) =>
jsonRequest<UrmiOtpDocumentation>(`/api/v1/urmiotp/projects/${projectId}/documentation`),
generate: (payload: {
project_name: string
template_type: string
to: string
variables: { otp: string; validity_minutes: string }
}) => jsonRequest<{
success: boolean
log_id: string
project_name: string
template_type: string
to: string
message_preview: string
status: string
}>('/api/v1/urmiotp/generate', {
method: 'POST',
body: JSON.stringify(payload),
}),
}
export type PhoneNumber = {
id: string
phone_number: string
monthly_number_price: number
inbound_price: number
outbound_price: number
currency: string
status: 'available' | 'assigned'
acquisition_type: 'admin_assigned' | 'purchased' | null
assigned_at: string | null
created_at: string
}
export type IntegrationEndpoint = {
id: string
name: string
phone_number_id: string
phone_number: string
url: string
enabled: boolean
created_at: string
updated_at: string
signing_secret?: string
}
export type VoiceSocketEndpoint = IntegrationEndpoint
export type SmsDeliveryWebhook = IntegrationEndpoint & {
description: string | null
event: 'message.delivery.updated'
}
export type IntegrationsDocs = {
title: string
summary: string
relay: {
principle: string
latency: string
}
socket: {
title: string
description: string
events: string[]
authentication: {
headers: string[]
signature: string
replay_protection: string
}
}
webhook: {
title: string
description: string
event: string
statuses: string[]
authentication: {
headers: string[]
signature: string
}
example: Record<string, unknown>
}
api_authentication: {
header: string
format: string
note: string
}
endpoints: Array<{ method: string; path: string; description: string }>
}
type EndpointPayload = {
name: string
phone_number_id: string
url: string
description?: string | null
enabled?: boolean
}
export const integrationsApi = {
docs: () => jsonRequest<IntegrationsDocs>('/api/v1/integrations/docs'),
listSockets: () => jsonRequest<VoiceSocketEndpoint[]>('/api/v1/integrations/sockets'),
createSocket: (payload: EndpointPayload) =>
jsonRequest<VoiceSocketEndpoint>('/api/v1/integrations/sockets', {
method: 'POST',
body: JSON.stringify(payload),
}),
updateSocket: (id: string, payload: Partial<EndpointPayload>) =>
jsonRequest<VoiceSocketEndpoint>(`/api/v1/integrations/sockets/${id}`, {
method: 'PATCH',
body: JSON.stringify(payload),
}),
deleteSocket: (id: string) =>
jsonRequest<void>(`/api/v1/integrations/sockets/${id}`, { method: 'DELETE' }),
listWebhooks: () => jsonRequest<SmsDeliveryWebhook[]>('/api/v1/integrations/webhooks'),
createWebhook: (payload: EndpointPayload) =>
jsonRequest<SmsDeliveryWebhook>('/api/v1/integrations/webhooks', {
method: 'POST',
body: JSON.stringify(payload),
}),
updateWebhook: (id: string, payload: Partial<EndpointPayload>) =>
jsonRequest<SmsDeliveryWebhook>(`/api/v1/integrations/webhooks/${id}`, {
method: 'PATCH',
body: JSON.stringify(payload),
}),
deleteWebhook: (id: string) =>
jsonRequest<void>(`/api/v1/integrations/webhooks/${id}`, { method: 'DELETE' }),
testWebhook: (id: string) =>
jsonRequest<{ delivered: boolean; response_code: number }>(
`/api/v1/integrations/webhooks/${id}/test`,
{ method: 'POST' },
),
}
export type VoiceCall = {
id: string
direction: string
from: string
to: string
status: string
stream_id: string | null
call_id: string | null
bridge_mode: string
upstream: string | null
error_message: string | null
answered_at: string | null
ended_at: string | null
created_at: string
media_url?: string
}
export type VoiceApiDocs = {
title: string
summary: string
method: string
path: string
authentication: {
header: string
format: string
alternate_header?: string
}
request_body: Record<string, {
type: string
required: boolean
example: string
description?: string
}>
response_example: Record<string, unknown>
relay: {
principle: string
latency: string
}
outbound_flow: string[]
endpoints: Array<{ method: string; path: string; description: string }>
}
export const voiceApi = {
docs: () => jsonRequest<VoiceApiDocs>('/api/v1/voice/docs'),
createCall: (payload: { to: string; from: string; bridge_mode?: 'console' | 'upstream'; upstream?: string }) =>
jsonRequest<VoiceCall>('/api/v1/voice/calls', { method: 'POST', body: JSON.stringify(payload) }),
getCall: (callId: string) => jsonRequest<VoiceCall>(`/api/v1/voice/calls/${callId}`),
createCallWithApiKey: (
apiKey: string,
payload: { to: string; from: string; bridge_mode?: 'console' | 'upstream'; upstream?: string },
) =>
fetch(`${API_BASE_URL}/api/v1/voice/calls`, {
method: 'POST',
credentials: 'include',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
}).then(async (response) => {
if (!response.ok) {
const body = await response.json().catch(() => null)
throw new Error(typeof body?.detail === 'string' ? body.detail : 'Request failed')
}
return response.json() as Promise<VoiceCall>
}),
}
export type AdminPhoneNumber = PhoneNumber & {
user: { id: string; name: string; email: string } | null
}
export const numbersApi = {
catalog: () => jsonRequest<PhoneNumber[]>('/api/v1/numbers/catalog'),
mine: () => jsonRequest<PhoneNumber[]>('/api/v1/numbers/mine'),
purchase: (numberId: string) =>
jsonRequest<PhoneNumber>(`/api/v1/numbers/${numberId}/purchase`, { method: 'POST' }),
}
export type AdminTemplateConfig = TemplateConfig & {
user: { id: string; name: string; email: string }
}
export const adminApi = {
me: () => jsonRequest<{ username: string }>('/api/v1/admin/me'),
login: (username: string, password: string) =>
jsonRequest<{ username: string }>('/api/v1/admin/login', {
method: 'POST',
body: JSON.stringify({ username, password }),
}),
logout: () => fetch(`${API_BASE_URL}/api/v1/admin/logout`, { method: 'POST', credentials: 'include' }),
listKyc: () => jsonRequest<AdminKycApplication[]>('/api/v1/admin/compliance/kyc'),
reviewDocument: (applicationId: string, documentId: string, status: 'approved' | 'rejected', review_note?: string) =>
jsonRequest<AdminKycApplication>(`/api/v1/admin/compliance/kyc/${applicationId}/documents/${documentId}`, {
method: 'PATCH',
body: JSON.stringify({ status, review_note: review_note || null }),
}),
listDlt: () => jsonRequest<AdminDltApplication[]>('/api/v1/admin/compliance/dlt'),
reviewDltDocument: (applicationId: string, documentId: string, status: 'approved' | 'rejected', review_note?: string) =>
jsonRequest<AdminDltApplication>(`/api/v1/admin/compliance/dlt/${applicationId}/documents/${documentId}`, {
method: 'PATCH',
body: JSON.stringify({ status, review_note: review_note || null }),
}),
listSenders: () => jsonRequest<AdminSenderConfig[]>('/api/v1/admin/compliance/senders'),
reviewSender: (senderId: string, status: 'approved' | 'rejected', review_note?: string) =>
jsonRequest<AdminSenderConfig>(`/api/v1/admin/compliance/senders/${senderId}`, {
method: 'PATCH',
body: JSON.stringify({ status, review_note: review_note || null }),
}),
listTemplates: () => jsonRequest<AdminTemplateConfig[]>('/api/v1/admin/compliance/templates'),
reviewTemplate: (templateId: string, status: 'approved' | 'rejected', review_note?: string) =>
jsonRequest<AdminTemplateConfig>(`/api/v1/admin/compliance/templates/${templateId}`, {
method: 'PATCH',
body: JSON.stringify({ status, review_note: review_note || null }),
}),
listUrmiOtpProjects: () =>
jsonRequest<AdminUrmiOtpProject[]>('/api/v1/admin/compliance/urmiotp-projects'),
reviewUrmiOtpProject: (projectId: string, status: 'approved' | 'rejected', review_note?: string) =>
jsonRequest<AdminUrmiOtpProject>(`/api/v1/admin/compliance/urmiotp-projects/${projectId}`, {
method: 'PATCH',
body: JSON.stringify({ status, review_note: review_note || null }),
}),
listPhoneNumbers: () => jsonRequest<AdminPhoneNumber[]>('/api/v1/admin/phone-numbers'),
createPhoneNumber: (payload: {
phone_number: string
monthly_number_price: number
inbound_price: number
outbound_price: number
user_email?: string | null
}) => jsonRequest<AdminPhoneNumber>('/api/v1/admin/phone-numbers', {
method: 'POST',
body: JSON.stringify(payload),
}),
updatePhoneNumber: (numberId: string, payload: {
monthly_number_price?: number
inbound_price?: number
outbound_price?: number
user_email?: string | null
release?: boolean
}) => jsonRequest<AdminPhoneNumber>(`/api/v1/admin/phone-numbers/${numberId}`, {
method: 'PATCH',
body: JSON.stringify(payload),
}),
}
export type LogType = 'outbound' | 'inbound' | 'sms' | 'otp'
export type LogEntry = {
id: string
from?: string
to?: string
template?: string
otp_type?: string
project?: string
socket_name?: string
status?: string
time: string
}
export type LogsResponse = {
type: LogType
page: number
page_size: number
total: number
total_pages: number
from: string
to: string
items: LogEntry[]
}
export const logsApi = {
list: (params: { type: LogType; from: string; to: string; page?: number }) => {
const query = new URLSearchParams({
type: params.type,
from: params.from,
to: params.to,
page: String(params.page ?? 1),
})
return jsonRequest<LogsResponse>(`/api/v1/logs?${query.toString()}`)
},
}
export type AnalyticsPeriod = 'weekly' | 'monthly' | 'quarterly'
export type AnalyticsService = 'sms' | 'inbound' | 'outbound' | 'otp'
export type AnalyticsGroupBy = 'dlt_sender' | 'dlt_template' | 'did' | 'socket' | 'project' | 'otp_type'
export type UsageAnalytics = {
period: AnalyticsPeriod
service: AnalyticsService
group_by: AnalyticsGroupBy
from: string
to: string
totals: {
sms: number
inbound: number
outbound: number
otp: number
}
timeline: Array<{
label: string
period_start: string
sms: number
inbound: number
outbound: number
otp: number
}>
breakdown: Array<{
label: string
detail: string
count: number
}>
}
export const analyticsApi = {
usage: (params: { period: AnalyticsPeriod; service: AnalyticsService; group_by: AnalyticsGroupBy }) => {
const query = new URLSearchParams({
period: params.period,
service: params.service,
group_by: params.group_by,
})
return jsonRequest<UsageAnalytics>(`/api/v1/analytics/usage?${query.toString()}`)
},
}
export type WhatsAppSetup = {
webhook_url: string
verify_token_configured: boolean
app_secret_configured: boolean
access_token_configured: boolean
phone_number_id: string | null
business_account_id: string | null
meta_app_id: string | null
api_version: string
recommended_fields: string[]
docs_url: string
}
export type WhatsAppWebhookEvent = {
id: string
waba_id: string | null
phone_number_id: string | null
field: string
event_type: string
from_wa_id: string | null
message_id: string | null
status: string | null
created_at: string
}
export const whatsappApi = {
setup: () => jsonRequest<WhatsAppSetup>('/api/v1/whatsapp/setup'),
events: () => jsonRequest<WhatsAppWebhookEvent[]>('/api/v1/whatsapp/events'),
sendText: (payload: { to: string; body: string }) =>
jsonRequest<Record<string, unknown>>('/api/v1/whatsapp/messages/send', {
method: 'POST',
body: JSON.stringify(payload),
}),
}