1444 lines
43 KiB
TypeScript
1444 lines
43 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 type AgentTemplateParameter = {
|
|
name: string
|
|
type: string
|
|
required: boolean
|
|
description: string
|
|
example: string
|
|
}
|
|
|
|
export type AgentMessageTemplate = {
|
|
id: string
|
|
slug: string
|
|
name: string
|
|
description: string
|
|
header_text: string | null
|
|
body_preview: string
|
|
footer_text: string | null
|
|
meta_template_name: string
|
|
language: string
|
|
category: string
|
|
parameters: AgentTemplateParameter[]
|
|
button_label: string | null
|
|
button_url_base: string | null
|
|
is_active: boolean
|
|
subscribed: boolean
|
|
}
|
|
|
|
export type AgentTemplateDocumentation = {
|
|
template: AgentMessageTemplate
|
|
api: {
|
|
method: string
|
|
path: string
|
|
summary: string
|
|
authentication: {
|
|
type: string
|
|
header: string
|
|
format: string
|
|
alternate_header?: string
|
|
}
|
|
parameters: Record<string, string>
|
|
example: Record<string, string>
|
|
response_example: Record<string, unknown>
|
|
}
|
|
notes: string[]
|
|
}
|
|
|
|
export type AgentMessageApiDoc = {
|
|
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> }>
|
|
notes?: string[]
|
|
available_templates?: Array<{ slug: string; name: string; meta_template_name: string }>
|
|
}
|
|
|
|
export type AgentMessageSendResponse = {
|
|
success: boolean
|
|
template: string
|
|
to: string
|
|
customer_name: string
|
|
report_group: string
|
|
report_id: string
|
|
report_url: string | null
|
|
wa_message_id: string | null
|
|
dispatch_id: string
|
|
}
|
|
|
|
export const agentTemplatesApi = {
|
|
list: () => jsonRequest<AgentMessageTemplate[]>('/api/v1/agent-templates'),
|
|
subscribe: (slug: string) =>
|
|
jsonRequest<AgentMessageTemplate>(`/api/v1/agent-templates/${slug}/subscribe`, { method: 'POST' }),
|
|
unsubscribe: (slug: string) =>
|
|
jsonRequest<AgentMessageTemplate>(`/api/v1/agent-templates/${slug}/subscribe`, { method: 'DELETE' }),
|
|
documentation: (slug: string) =>
|
|
jsonRequest<AgentTemplateDocumentation>(`/api/v1/agent-templates/${slug}/docs`),
|
|
testStatus: (slug: string) =>
|
|
jsonRequest<{ template: string; allowed: boolean; retry_after?: string | null; last_tested_at?: string | null }>(
|
|
`/api/v1/agent-templates/${slug}/test-status`,
|
|
),
|
|
test: (slug: string, payload: { to: string; customer_name: string; report_group: string; report_id?: string }) =>
|
|
jsonRequest<AgentMessageSendResponse>(`/api/v1/agent-templates/${slug}/test`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload),
|
|
}),
|
|
}
|
|
|
|
export const agentMessagesApi = {
|
|
docs: () => jsonRequest<AgentMessageApiDoc>('/api/v1/agent/messages/docs/send'),
|
|
send: (
|
|
apiKey: string,
|
|
payload: {
|
|
template: string
|
|
to: string
|
|
customer_name: string
|
|
report_group: string
|
|
report_id?: string
|
|
},
|
|
) =>
|
|
fetch(`${API_BASE_URL}/api/v1/agent/messages/send`, {
|
|
method: 'POST',
|
|
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<AgentMessageSendResponse>
|
|
}),
|
|
}
|
|
|
|
export type UrmiportGroup = {
|
|
id: string
|
|
report_group: string
|
|
display_name: string | null
|
|
default_access: 'public' | 'password'
|
|
password_protected: boolean
|
|
id_mode: 'auto' | 'manual'
|
|
report_count: number
|
|
url_pattern: string
|
|
created_at: string
|
|
}
|
|
|
|
export type UrmiportReport = {
|
|
id: string
|
|
report_group: string
|
|
report_id: string
|
|
access: 'public' | 'password'
|
|
password_protected: boolean
|
|
url: string
|
|
updated_at: string
|
|
created_at: string
|
|
}
|
|
|
|
export type UrmiportApiDoc = {
|
|
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> }>
|
|
notes?: string[]
|
|
}
|
|
|
|
export type UrmiportPublishResponse = UrmiportReport & { success: boolean }
|
|
|
|
export const urmiportApi = {
|
|
docs: () => jsonRequest<UrmiportApiDoc>('/api/v1/urmiport/docs'),
|
|
listGroups: () => jsonRequest<UrmiportGroup[]>('/api/v1/urmiport/groups'),
|
|
createGroup: (payload: {
|
|
report_group: string
|
|
display_name?: string
|
|
default_access: 'public' | 'password'
|
|
password?: string
|
|
id_mode: 'auto' | 'manual'
|
|
}) => jsonRequest<UrmiportGroup>('/api/v1/urmiport/groups', {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload),
|
|
}),
|
|
listReports: (slug: string) => jsonRequest<UrmiportReport[]>(`/api/v1/urmiport/groups/${slug}/reports`),
|
|
viewReport: (group: string, reportId: string) =>
|
|
fetch(`${API_BASE_URL}/api/v1/urmiport/view/${group}/${reportId}`).then(async (response) => {
|
|
if (!response.ok) {
|
|
const body = await response.json().catch(() => null)
|
|
throw new Error(typeof body?.detail === 'string' ? body.detail : 'Report not found')
|
|
}
|
|
return response.json() as Promise<{
|
|
report_group: string
|
|
report_id: string
|
|
access: string
|
|
requires_password: boolean
|
|
html: string | null
|
|
}>
|
|
}),
|
|
unlockReport: (group: string, reportId: string, password: string) =>
|
|
fetch(`${API_BASE_URL}/api/v1/urmiport/view/${group}/${reportId}/unlock`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ password }),
|
|
}).then(async (response) => {
|
|
if (!response.ok) {
|
|
const body = await response.json().catch(() => null)
|
|
throw new Error(typeof body?.detail === 'string' ? body.detail : 'Unable to unlock report')
|
|
}
|
|
return response.json() as Promise<{ html: string }>
|
|
}),
|
|
publish: (
|
|
apiKey: string,
|
|
payload: {
|
|
report_group: string
|
|
report_id?: string
|
|
body: string
|
|
access?: 'public' | 'password'
|
|
password?: string
|
|
},
|
|
) =>
|
|
fetch(`${API_BASE_URL}/api/v1/urmiport/reports`, {
|
|
method: 'POST',
|
|
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<UrmiportPublishResponse>
|
|
}),
|
|
}
|
|
|
|
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 type AdminWhatsAppConfig = {
|
|
business_phone: string
|
|
phone_number_id: string | null
|
|
business_account_id: string | null
|
|
meta_app_id: string | null
|
|
access_token_configured: boolean
|
|
app_secret_configured: boolean
|
|
webhook_url: string
|
|
verify_token_configured: boolean
|
|
ready_to_send: boolean
|
|
supported_operations: string[]
|
|
}
|
|
|
|
export type AdminWhatsAppMessage = {
|
|
id: string
|
|
direction: 'inbound' | 'outbound'
|
|
wa_message_id: string | null
|
|
from: string
|
|
to: string
|
|
body: string | null
|
|
message_type: string
|
|
status: string
|
|
media?: {
|
|
id?: string
|
|
mime_type?: string
|
|
caption?: string
|
|
filename?: string
|
|
} | null
|
|
created_at: string
|
|
}
|
|
|
|
export type AdminWhatsAppTemplate = {
|
|
id?: string
|
|
name: string
|
|
language: string
|
|
status?: string
|
|
category?: string
|
|
}
|
|
|
|
export type AdminWhatsAppSendPayload = {
|
|
to: string
|
|
message_type: 'text' | 'template' | 'image' | 'document' | 'audio' | 'video' | 'interactive_buttons' | 'interactive_list' | 'reaction'
|
|
text?: string
|
|
reply_to?: string
|
|
template_name?: string
|
|
template_language?: string
|
|
template_components?: Record<string, unknown>[]
|
|
media_link?: string
|
|
media_caption?: string
|
|
media_filename?: string
|
|
interactive_body?: string
|
|
interactive_header?: string
|
|
interactive_footer?: string
|
|
interactive_buttons?: Array<{ id: string; title: string }>
|
|
interactive_list_button?: string
|
|
interactive_list_sections?: Record<string, unknown>[]
|
|
reaction_message_id?: string
|
|
reaction_emoji?: string
|
|
}
|
|
|
|
export type AdminConnectionTokens = {
|
|
users: Array<{
|
|
id: string
|
|
name: string
|
|
email: string
|
|
whatsapp_accounts: Array<{
|
|
id: string
|
|
name: string
|
|
status: string
|
|
source: 'token' | 'signup'
|
|
access_token: string
|
|
waba_id: string
|
|
phones: Array<{ id: string; display_phone: string; phone_number_id: string; status: string }>
|
|
updated_at: string
|
|
}>
|
|
facebook: {
|
|
status: string
|
|
user_name: string
|
|
access_token: string
|
|
expires_at: string | null
|
|
connected_at: string | null
|
|
} | null
|
|
}>
|
|
}
|
|
|
|
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),
|
|
}),
|
|
connectionTokens: () => jsonRequest<AdminConnectionTokens>('/api/v1/admin/connection-tokens'),
|
|
whatsappConfig: () => jsonRequest<AdminWhatsAppConfig>('/api/v1/admin/whatsapp/config'),
|
|
updateWhatsAppConfig: (payload: {
|
|
phone_number_id?: string
|
|
business_account_id?: string
|
|
app_secret?: string
|
|
}) => jsonRequest<AdminWhatsAppConfig>('/api/v1/admin/whatsapp/config', {
|
|
method: 'PATCH',
|
|
body: JSON.stringify(payload),
|
|
}),
|
|
listWhatsAppMessages: (limit = 50, peer?: string) =>
|
|
jsonRequest<AdminWhatsAppMessage[]>(
|
|
`/api/v1/admin/whatsapp/messages?limit=${limit}${peer ? `&peer=${encodeURIComponent(peer)}` : ''}`,
|
|
),
|
|
listWhatsAppTemplates: () =>
|
|
jsonRequest<{ data?: AdminWhatsAppTemplate[] }>('/api/v1/admin/whatsapp/templates'),
|
|
markWhatsAppRead: (waMessageId: string) =>
|
|
jsonRequest<{ success?: boolean }>(`/api/v1/admin/whatsapp/messages/${encodeURIComponent(waMessageId)}/read`, {
|
|
method: 'POST',
|
|
}),
|
|
sendWhatsAppMessage: (payload: AdminWhatsAppSendPayload) =>
|
|
jsonRequest<AdminWhatsAppMessage>('/api/v1/admin/whatsapp/messages/send', {
|
|
method: 'POST',
|
|
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
|
|
verify_token_visible: boolean
|
|
api_version: string
|
|
recommended_fields: string[]
|
|
docs_url: string
|
|
}
|
|
|
|
export type WhatsAppSignupConfig = {
|
|
app_id: string | null
|
|
config_id: string | null
|
|
api_version: string
|
|
sdk_version?: string
|
|
ready: boolean
|
|
}
|
|
|
|
export type WhatsAppPhoneAsset = {
|
|
id: string
|
|
phone_number_id: string
|
|
display_phone: string
|
|
verified_name: string | null
|
|
quality: string | null
|
|
status: string
|
|
code_verification_status: string | null
|
|
needs_otp?: boolean
|
|
needs_registration?: boolean
|
|
}
|
|
|
|
export type WhatsAppAccount = {
|
|
id: string
|
|
waba_id_masked: string | null
|
|
name: string
|
|
business_name: string | null
|
|
business_id_masked: string | null
|
|
status: string
|
|
usable: boolean
|
|
messaging_status: string
|
|
management_status: string
|
|
templates_status: string
|
|
webhooks_status: string
|
|
template_count: number
|
|
quality: string | null
|
|
primary_phone: WhatsAppPhoneAsset | null
|
|
phones: WhatsAppPhoneAsset[]
|
|
last_error: string | null
|
|
updated_at: string
|
|
}
|
|
|
|
export type WhatsAppSignupResult = {
|
|
status: string
|
|
message: string
|
|
accounts: WhatsAppAccount[]
|
|
}
|
|
|
|
export type WhatsAppTemplateRow = {
|
|
name: string | null
|
|
category: string | null
|
|
language: string | null
|
|
status: string | null
|
|
quality: string | null
|
|
}
|
|
|
|
export type WhatsAppDeveloperConfig = {
|
|
webhook_url: string
|
|
verify_token_configured: boolean
|
|
verify_token_visible: boolean
|
|
recommended_fields: string[]
|
|
webhooks_status: string
|
|
docs_url: string
|
|
}
|
|
|
|
export type WhatsAppAccountLogs = {
|
|
audits: { id: string; event: string; detail: string; created_at: string }[]
|
|
events: { id: string; field: string; event_type: string; from_wa_id: string | null; status: string | null; created_at: string }[]
|
|
}
|
|
|
|
export type CustomerWhatsAppMessage = {
|
|
id: string
|
|
direction: 'inbound' | 'outbound'
|
|
wa_message_id: string | null
|
|
from: string
|
|
to: string
|
|
body: string | null
|
|
message_type: string
|
|
status: string
|
|
contact_name?: string | null
|
|
media?: { id?: string; mime_type?: string; caption?: string; filename?: string } | null
|
|
created_at: string
|
|
}
|
|
|
|
export type CustomerWhatsAppSend = {
|
|
to: string
|
|
message_type?: string
|
|
text?: string
|
|
reply_to?: string
|
|
template_name?: string
|
|
template_language?: string
|
|
media_link?: string
|
|
media_caption?: string
|
|
media_filename?: string
|
|
interactive_body?: string
|
|
interactive_buttons?: { id: string; title: string }[]
|
|
reaction_message_id?: string
|
|
reaction_emoji?: 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'),
|
|
signupConfig: () => jsonRequest<WhatsAppSignupConfig>('/api/v1/whatsapp/signup/config'),
|
|
accounts: () => jsonRequest<WhatsAppAccount[]>('/api/v1/whatsapp/accounts'),
|
|
connectToken: (payload: { access_token: string; waba_id: string; phone_number_id?: string | null; app_secret?: string | null }) =>
|
|
jsonRequest<WhatsAppSignupResult>('/api/v1/whatsapp/connections/token', {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload),
|
|
}),
|
|
completeSignup: (payload: {
|
|
event: string
|
|
code?: string | null
|
|
waba_id?: string | null
|
|
phone_number_id?: string | null
|
|
business_id?: string | null
|
|
error_message?: string | null
|
|
}) =>
|
|
jsonRequest<WhatsAppSignupResult>('/api/v1/whatsapp/signup/complete', {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload),
|
|
}),
|
|
account: (accountId: string) => jsonRequest<WhatsAppAccount>(`/api/v1/whatsapp/accounts/${accountId}`),
|
|
refreshAccount: (accountId: string) =>
|
|
jsonRequest<WhatsAppAccount>(`/api/v1/whatsapp/accounts/${accountId}/refresh`, { method: 'POST' }),
|
|
disconnectAccount: (accountId: string) =>
|
|
jsonRequest<WhatsAppAccount>(`/api/v1/whatsapp/accounts/${accountId}/disconnect`, { method: 'POST' }),
|
|
registerPhone: (accountId: string, phoneId: string, pin?: string) =>
|
|
jsonRequest<WhatsAppAccount>(`/api/v1/whatsapp/accounts/${accountId}/phones/${phoneId}/register`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ pin: pin || null }),
|
|
}),
|
|
enableDelivery: (accountId: string, appSecret: string) =>
|
|
jsonRequest<WhatsAppAccount>(`/api/v1/whatsapp/accounts/${accountId}/delivery`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ app_secret: appSecret }),
|
|
}),
|
|
messages: (accountId: string) =>
|
|
jsonRequest<CustomerWhatsAppMessage[]>(`/api/v1/whatsapp/accounts/${accountId}/messages`),
|
|
sendAccountMessage: (accountId: string, payload: CustomerWhatsAppSend) =>
|
|
jsonRequest<CustomerWhatsAppMessage>(`/api/v1/whatsapp/accounts/${accountId}/messages/send`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload),
|
|
}),
|
|
templates: (accountId: string) =>
|
|
jsonRequest<{ available: boolean; templates: WhatsAppTemplateRow[] }>(`/api/v1/whatsapp/accounts/${accountId}/templates`),
|
|
createMarketingTemplate: (accountId: string, payload: { name: string; language: string; body: string }) =>
|
|
jsonRequest<{ id?: string | null; name: string; language: string; category: string; status: string }>(
|
|
`/api/v1/whatsapp/accounts/${accountId}/templates`,
|
|
{ method: 'POST', body: JSON.stringify(payload) },
|
|
),
|
|
sendBroadcast: (accountId: string, payload: {
|
|
template_name: string
|
|
template_language: string
|
|
recipients: string[]
|
|
body_parameters?: string[]
|
|
product_policy?: 'STRICT' | 'CLOUD_API_FALLBACK'
|
|
use_sample_header?: boolean
|
|
}) =>
|
|
jsonRequest<{
|
|
template_name: string
|
|
template_language: string
|
|
product_policy: string
|
|
sent: number
|
|
failed: number
|
|
results: { to: string; status: string; error?: string; message_id?: string | null }[]
|
|
}>(`/api/v1/whatsapp/accounts/${accountId}/broadcasts`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload),
|
|
}),
|
|
logs: (accountId: string) => jsonRequest<WhatsAppAccountLogs>(`/api/v1/whatsapp/accounts/${accountId}/logs`),
|
|
developer: (accountId: string) => jsonRequest<WhatsAppDeveloperConfig>(`/api/v1/whatsapp/accounts/${accountId}/developer`),
|
|
sendText: (payload: { to: string; body: string; phone_number_id: string }) =>
|
|
jsonRequest<Record<string, unknown>>('/api/v1/whatsapp/messages/send', {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload),
|
|
}),
|
|
}
|
|
|
|
export type WaTemplateDraft = {
|
|
account_id: string
|
|
phone_number_id: string
|
|
name: string
|
|
language: string
|
|
category: 'MARKETING' | 'UTILITY' | 'AUTHENTICATION' | ''
|
|
subtype: string
|
|
parameter_format: 'POSITIONAL' | 'NAMED'
|
|
header: {
|
|
type: 'NONE' | 'TEXT' | 'IMAGE' | 'VIDEO' | 'DOCUMENT' | 'LOCATION'
|
|
text: string
|
|
examples: string[]
|
|
handle: string
|
|
sample_id: string
|
|
sample_name: string
|
|
sample_type: string
|
|
preview_name: string
|
|
preview_url: string
|
|
}
|
|
body: string
|
|
body_examples: string[]
|
|
footer: string
|
|
buttons: {
|
|
type: 'URL' | 'PHONE_NUMBER' | 'QUICK_REPLY' | 'COPY_CODE' | 'CATALOG' | 'FLOW'
|
|
text: string
|
|
url: string
|
|
phone: string
|
|
example: string
|
|
flow_id: string
|
|
screen: string
|
|
}[]
|
|
auth: {
|
|
otp_type: 'COPY_CODE'
|
|
add_security_recommendation: boolean
|
|
code_expiration_minutes: number
|
|
copy_label: string
|
|
}
|
|
}
|
|
|
|
export type WaManagedTemplate = {
|
|
id: string
|
|
account_id: string
|
|
account_name: string
|
|
waba_name: string
|
|
connection_name: string
|
|
connection_method: string
|
|
phone_number_id: string | null
|
|
display_phone: string
|
|
phones: string[]
|
|
meta_template_id: string | null
|
|
name: string
|
|
language: string
|
|
category: string
|
|
subtype: string
|
|
status: string
|
|
quality: string | null
|
|
rejected_reason: string | null
|
|
parameter_format: string
|
|
header_format: string
|
|
sample_stored: boolean
|
|
sample_name: string
|
|
definition: Partial<WaTemplateDraft>
|
|
components: unknown[]
|
|
messages_sent: number
|
|
messages_sent_source: string
|
|
created_at: string
|
|
updated_at: string
|
|
analytics?: {
|
|
source: string
|
|
note: string
|
|
sent: number
|
|
by_status: Record<string, number>
|
|
}
|
|
}
|
|
|
|
export const waTemplateApi = {
|
|
languages: () => jsonRequest<{ code: string; label: string }[]>('/api/v1/whatsapp/message-templates/languages'),
|
|
list: (accountId?: string) =>
|
|
jsonRequest<{ templates: WaManagedTemplate[]; summary: { total: number; approved: number; pending: number; rejected: number; paused: number }; warnings: string[] }>(
|
|
`/api/v1/whatsapp/message-templates${accountId ? `?account_id=${accountId}` : ''}`,
|
|
),
|
|
get: (templateId: string) => jsonRequest<WaManagedTemplate>(`/api/v1/whatsapp/message-templates/${templateId}`),
|
|
create: (payload: WaTemplateDraft) =>
|
|
jsonRequest<WaManagedTemplate>('/api/v1/whatsapp/message-templates', { method: 'POST', body: JSON.stringify(payload) }),
|
|
resubmit: (templateId: string, payload: WaTemplateDraft) =>
|
|
jsonRequest<WaManagedTemplate>(`/api/v1/whatsapp/message-templates/${templateId}/resubmit`, { method: 'POST', body: JSON.stringify(payload) }),
|
|
remove: (templateId: string) =>
|
|
jsonRequest<{ status: string }>(`/api/v1/whatsapp/message-templates/${templateId}`, { method: 'DELETE' }),
|
|
uploadHeader: (accountId: string, file: File) => {
|
|
const body = new FormData()
|
|
body.append('account_id', accountId)
|
|
body.append('file', file)
|
|
return jsonRequest<{ handle: string; format: string; sample_id: string; sample_name: string; sample_type: string }>('/api/v1/whatsapp/message-templates/media', { method: 'POST', body })
|
|
},
|
|
saveSample: (templateId: string, file: File) => {
|
|
const body = new FormData()
|
|
body.append('file', file)
|
|
return jsonRequest<WaManagedTemplate>(`/api/v1/whatsapp/message-templates/${templateId}/sample`, { method: 'POST', body })
|
|
},
|
|
sampleBlob: async (templateId: string) => {
|
|
const response = await fetch(`${API_BASE_URL}/api/v1/whatsapp/message-templates/${templateId}/sample`, { credentials: 'include' })
|
|
if (!response.ok) throw new Error('The sample file could not be opened')
|
|
return response.blob()
|
|
},
|
|
}
|