Is your feature request related to a problem?
Many data fetches are not handling errors, nor validating data types.
Describe the solution you'd like
This issue identifies areas where Zod schemas can be used to validate:
- API endpoint inputs (query parameters, request bodies)
- API endpoint return values (response data)
- External API responses (third-party API data)
Current State
- Zod is already installed (
zod@^4.1.12 in package.json)
- Zod is currently used in
pages/api/usage.tsx to validate external API responses (DayDataSchema)
- Basic address validation exists via
isValidAddress() helper, but could be enhanced with Zod
1. API Endpoint Input Validation
1.1 Address-based Endpoints
These endpoints accept an address query parameter that should be validated:
/api/account-balance/[address].tsx
- Input:
address query param
- Current validation:
isValidAddress(address) - basic string check
- Zod opportunity: Create
AddressSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/) for stricter validation
/api/ens-data/[address].tsx
- Input:
address query param
- Current validation:
isValidAddress(address) + blacklist check
- Zod opportunity: Same as above, plus validate blacklist separately
/api/score/[address].tsx
- Input:
address query param
- Current validation:
isValidAddress(address)
- Zod opportunity: Address validation schema
/api/pending-stake/[address].tsx
- Input:
address query param
- Current validation:
isValidAddress(address)
- Zod opportunity: Address validation schema
/api/l1-delegator/[address].tsx
- Input:
address query param
- Current validation:
isValidAddress(address)
- Zod opportunity: Address validation schema
1.2 Treasury/Proposal Endpoints
/api/treasury/proposal/[proposalId]/state.tsx
- Input:
proposalId query param (string)
- Current validation: Basic existence check
if (!proposalId)
- Zod opportunity:
const ProposalIdSchema = z.string().regex(/^\d+$/) // numeric string
/api/treasury/proposal/[proposalId]/votes/[address].tsx
- Input:
proposalId query param
address query param
- Current validation: Basic checks
- Zod opportunity: Combined schema for both params
/api/treasury/votes/[address]/index.tsx
- Input:
address query param
- Current validation:
isValidAddress(address)
- Zod opportunity: Address validation schema
/api/treasury/votes/[address]/registered.tsx
- Input:
address query param
- Current validation:
isValidAddress(address)
- Zod opportunity: Address validation schema
1.3 POST Endpoints with Request Bodies
/api/upload-ipfs.tsx
- Input:
req.body (JSON object)
- Current validation: None - directly passes
req.body to external API
- Zod opportunity:
const IpfsUploadSchema = z.object({
// Define expected structure of IPFS upload data
// This depends on what data is actually being uploaded
})
/api/generateProof.tsx
- Input:
req.body with { account, delegate, stake, fees }
- Current validation: None - directly destructures from
req.body
- Zod opportunity:
const GenerateProofSchema = z.object({
account: z.string().regex(/^0x[a-fA-F0-9]{40}$/),
delegate: z.string().regex(/^0x[a-fA-F0-9]{40}$/),
stake: z.string(), // or z.bigint() if needed
fees: z.string(), // or z.bigint() if needed
})
1.4 Query Parameters with Optional Values
/api/pipelines/index.tsx
- Input: Optional
region query param
- Current validation: None
- Zod opportunity:
const PipelinesQuerySchema = z.object({
region: z.string().optional(),
})
/api/score/index.tsx
- Input: Optional
pipeline and model query params
- Current validation: None
- Zod opportunity:
const ScoreQuerySchema = z.object({
pipeline: z.string().optional(),
model: z.string().optional(),
})
2. API Endpoint Return Value Validation
All API endpoints return typed data, but there's no runtime validation. Adding Zod schemas would catch contract/API changes early.
2.1 Account/Balance Endpoints
/api/account-balance/[address].tsx
- Return type:
AccountBalance
- Zod opportunity:
const AccountBalanceSchema = z.object({
balance: z.string(),
allowance: z.string(),
})
/api/pending-stake/[address].tsx
- Return type:
PendingFeesAndStake
- Zod opportunity:
const PendingFeesAndStakeSchema = z.object({
pendingStake: z.string(),
pendingFees: z.string(),
})
/api/l1-delegator/[address].tsx
- Return type:
L1Delegator
- Zod opportunity:
const UnbondingLockSchema = z.object({
id: z.number(),
amount: z.string(),
withdrawRound: z.string(),
})
const L1DelegatorSchema = z.object({
delegateAddress: z.string(),
pendingStake: z.string(),
pendingFees: z.string(),
transcoderStatus: z.enum(["not-registered", "registered"]),
unbondingLocks: z.array(UnbondingLockSchema),
activeLocks: z.array(UnbondingLockSchema),
})
2.2 ENS Endpoints
/api/ens-data/[address].tsx
- Return type:
EnsIdentity
- Zod opportunity:
const EnsIdentitySchema = z.object({
id: z.string(),
idShort: z.string(),
avatar: z.string().nullable().optional(),
name: z.string().nullable().optional(),
url: z.string().nullable().optional(),
twitter: z.string().nullable().optional(),
github: z.string().nullable().optional(),
description: z.string().nullable().optional(),
})
2.3 Round/Protocol Endpoints
/api/current-round.tsx
- Return type:
CurrentRoundInfo
- Zod opportunity:
const CurrentRoundInfoSchema = z.object({
id: z.number(),
startBlock: z.number(),
initialized: z.boolean(),
currentL1Block: z.number(),
currentL2Block: z.number(),
})
2.4 Performance/Score Endpoints
/api/score/[address].tsx
- Return type:
PerformanceMetrics
- Zod opportunity:
const RegionalValuesSchema = z.record(z.string(), z.number())
const ScoreSchema = z.object({
value: z.number(),
region: z.string(),
model: z.string(),
pipeline: z.string(),
orchestrator: z.string(),
})
const PerformanceMetricsSchema = z.object({
successRates: RegionalValuesSchema,
roundTripScores: RegionalValuesSchema,
scores: RegionalValuesSchema,
pricePerPixel: z.number(),
topAIScore: ScoreSchema,
})
/api/score/index.tsx
- Return type:
AllPerformanceMetrics
- Zod opportunity:
const AllPerformanceMetricsSchema = z.record(
z.string(),
PerformanceMetricsSchema
)
2.5 Treasury Endpoints
/api/treasury/proposal/[proposalId]/state.tsx
- Return type:
ProposalState
- Zod opportunity:
const ProposalStateSchema = z.object({
id: z.string(),
state: z.enum([
"Pending",
"Active",
"Canceled",
"Defeated",
"Succeeded",
"Queued",
"Expired",
"Executed",
"Unknown",
]),
quota: z.string(),
quorum: z.string(),
totalVoteSupply: z.string(),
votes: z.object({
against: z.string(),
for: z.string(),
abstain: z.string(),
}),
})
/api/treasury/votes/[address]/index.tsx
- Return type:
VotingPower
- Zod opportunity:
const VotingPowerSchema = z.object({
proposalThreshold: z.string(),
self: z.object({
address: z.string().regex(/^0x[a-fA-F0-9]{40}$/),
votes: z.string(),
}),
delegate: z.object({
address: z.string().regex(/^0x[a-fA-F0-9]{40}$/),
votes: z.string(),
}).optional(),
})
/api/treasury/votes/[address]/registered.tsx
- Return type:
RegisteredToVote
- Zod opportunity:
const RegisteredToVoteSchema = z.object({
registered: z.boolean(),
delegate: z.object({
address: z.string().regex(/^0x[a-fA-F0-9]{40}$/),
registered: z.boolean(),
}),
})
/api/treasury/proposal/[proposalId]/votes/[address].tsx
- Return type:
ProposalVotingPower
- Zod opportunity:
const ProposalVotingPowerSchema = z.object({
self: z.object({
address: z.string().regex(/^0x[a-fA-F0-9]{40}$/),
votes: z.string(),
hasVoted: z.boolean(),
}),
delegate: z.object({
address: z.string().regex(/^0x[a-fA-F0-9]{40}$/),
votes: z.string(),
hasVoted: z.boolean(),
}).optional(),
})
2.6 Pipeline/Region Endpoints
/api/pipelines/index.tsx
- Return type:
AvailablePipelines
- Zod opportunity:
const PipelineSchema = z.object({
id: z.string(),
models: z.array(z.string()),
regions: z.array(z.string()),
})
const AvailablePipelinesSchema = z.object({
pipelines: z.array(PipelineSchema),
})
/api/regions/index.ts
- Return type:
Regions
- Zod opportunity:
const RegionSchema = z.object({
id: z.string(),
name: z.string(),
type: z.enum(["transcoding", "ai"]),
})
const RegionsSchema = z.object({
regions: z.array(RegionSchema),
})
2.7 Usage/Chart Endpoints
/api/usage.tsx
- Return type:
HomeChartData
- Current validation: Already uses
DayDataSchema for external API response
- Zod opportunity: Create schema for the full
HomeChartData return type
/api/upload-ipfs.tsx
- Return type:
AddIpfs
- Zod opportunity:
const AddIpfsSchema = z.object({
hash: z.string(),
})
3. External API Response Validation
These endpoints fetch data from external APIs and should validate responses before using them.
3.1 Metrics/AI Server Responses
/api/score/[address].tsx
- External APIs:
NEXT_PUBLIC_AI_METRICS_SERVER_URL/api/top_ai_score → ScoreResponse
NEXT_PUBLIC_METRICS_SERVER_URL/api/aggregated_stats → MetricsResponse
- Pricing URL →
PriceResponse
- Current validation: None - directly uses
await response.json()
- Zod opportunity:
const ScoreResponseSchema = z.object({
value: z.number(),
region: z.string(),
model: z.string(),
pipeline: z.string(),
orchestrator: z.string(),
})
const MetricSchema = z.object({
success_rate: z.number(),
round_trip_score: z.number(),
score: z.number(),
})
const MetricsResponseSchema = z.record(
z.string(),
z.record(z.string(), MetricSchema).optional()
)
const PriceResponseSchema = z.array(z.object({
Address: z.string(),
ServiceURI: z.string(),
LastRewardRound: z.number(),
RewardCut: z.number(),
FeeShare: z.number(),
DelegatedStake: z.string(),
ActivationRound: z.number(),
DeactivationRound: z.string(),
Active: z.boolean(),
Status: z.string(),
PricePerPixel: z.number(),
UpdatedAt: z.number(),
}))
/api/score/index.tsx
- External APIs: Same as above
- Current validation: None
- Zod opportunity: Same schemas as above
/api/pipelines/index.tsx
- External API:
NEXT_PUBLIC_AI_METRICS_SERVER_URL/api/pipelines
- Current validation: None - directly uses
await response.json()
- Zod opportunity: Use
AvailablePipelinesSchema (defined in section 2.6)
/api/regions/index.ts
- External APIs:
NEXT_PUBLIC_METRICS_SERVER_URL/api/regions
NEXT_PUBLIC_AI_METRICS_SERVER_URL/api/regions
- Current validation: None
- Zod opportunity: Use
RegionsSchema (defined in section 2.6)
3.2 Livepeer.com API
/api/usage.tsx
- External API:
https://livepeer.com/data/usage/query/total
- Current validation: ✅ Already uses
DayDataSchema with safeParse()
- Status: Good example of proper validation
3.3 Pinata IPFS API
/api/upload-ipfs.tsx
- External API:
https://api.pinata.cloud/pinning/pinJSONToIPFS
- Current validation: None - directly uses
await fetchResult.json()
- Zod opportunity:
const PinataResponseSchema = z.object({
IpfsHash: z.string(),
// Add other fields if Pinata returns more
})
3.4 ENS Provider Responses
lib/api/ens.ts → getEnsForAddress()
- External API: Ethereum provider ENS lookups
- Current validation: None - directly uses provider responses
- Zod opportunity: Validate ENS resolver text records and avatar URLs
4. SSR/Client-Side API Calls
4.1 Server-Side Rendering
lib/api/ssr.ts → getEnsIdentity()
- Internal API call:
/api/ens-data/${address}
- Current validation: None - directly uses
await response.json()
- Zod opportunity: Use
EnsIdentitySchema to validate the response
5. Recommended Implementation Strategy
Phase 1: Shared Schemas
- Create
lib/api/schemas/ directory
- Create shared schemas for common types:
address.ts - Address validation
common.ts - Common types (strings, numbers, etc.)
ens.ts - ENS-related schemas
treasury.ts - Treasury/proposal schemas
performance.ts - Performance metrics schemas
Phase 2: Input Validation
- Add input validation to all endpoints with query params
- Add request body validation to POST endpoints
- Return proper 400 errors with validation details
Phase 3: Output Validation
- Add return value validation to all endpoints
- Log validation errors (don't fail in production, but log for monitoring)
- Consider failing in development mode to catch issues early
Phase 4: External API Validation
- Validate all external API responses
- Use
safeParse() to handle validation errors gracefully
- Add retry logic or fallback values where appropriate
Example Implementation Pattern
// lib/api/schemas/address.ts
import { z } from "zod";
export const AddressSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/);
// lib/api/schemas/account-balance.ts
import { z } from "zod";
export const AccountBalanceSchema = z.object({
balance: z.string(),
allowance: z.string(),
});
// pages/api/account-balance/[address].tsx
import { AddressSchema } from "@lib/api/schemas/address";
import { AccountBalanceSchema } from "@lib/api/schemas/account-balance";
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
// Validate input
const addressResult = AddressSchema.safeParse(req.query.address);
if (!addressResult.success) {
return res.status(400).json({ error: "Invalid address", details: addressResult.error });
}
// ... fetch data ...
// Validate output
const result = AccountBalanceSchema.safeParse(accountBalance);
if (!result.success) {
console.error("Account balance validation failed:", result.error);
// In production, might still return the data, but log the error
// In development, could return 500 to catch issues early
}
return res.status(200).json(accountBalance);
};
Summary
Total Opportunities:
- Input Validation: ~15 endpoints need query param/body validation
- Output Validation: ~20 endpoints need return value validation
- External API Validation: ~8 external API calls need response validation
Priority:
- High: External API responses (can cause runtime errors)
- Medium: POST request bodies (security/data integrity)
- Medium: Return value validation (catch contract changes)
- Low: Query parameter validation (basic checks exist)
Estimated Impact:
- Better error messages for invalid inputs
- Early detection of API contract changes
- Protection against malformed external API responses
- Improved type safety at runtime
Describe alternatives you've considered
No response
Additional context
No response
Is your feature request related to a problem?
Many data fetches are not handling errors, nor validating data types.
Describe the solution you'd like
This issue identifies areas where Zod schemas can be used to validate:
Current State
zod@^4.1.12in package.json)pages/api/usage.tsxto validate external API responses (DayDataSchema)isValidAddress()helper, but could be enhanced with Zod1. API Endpoint Input Validation
1.1 Address-based Endpoints
These endpoints accept an
addressquery parameter that should be validated:/api/account-balance/[address].tsxaddressquery paramisValidAddress(address)- basic string checkAddressSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/)for stricter validation/api/ens-data/[address].tsxaddressquery paramisValidAddress(address)+ blacklist check/api/score/[address].tsxaddressquery paramisValidAddress(address)/api/pending-stake/[address].tsxaddressquery paramisValidAddress(address)/api/l1-delegator/[address].tsxaddressquery paramisValidAddress(address)1.2 Treasury/Proposal Endpoints
/api/treasury/proposal/[proposalId]/state.tsxproposalIdquery param (string)if (!proposalId)/api/treasury/proposal/[proposalId]/votes/[address].tsxproposalIdquery paramaddressquery param/api/treasury/votes/[address]/index.tsxaddressquery paramisValidAddress(address)/api/treasury/votes/[address]/registered.tsxaddressquery paramisValidAddress(address)1.3 POST Endpoints with Request Bodies
/api/upload-ipfs.tsxreq.body(JSON object)req.bodyto external API/api/generateProof.tsxreq.bodywith{ account, delegate, stake, fees }req.body1.4 Query Parameters with Optional Values
/api/pipelines/index.tsxregionquery param/api/score/index.tsxpipelineandmodelquery params2. API Endpoint Return Value Validation
All API endpoints return typed data, but there's no runtime validation. Adding Zod schemas would catch contract/API changes early.
2.1 Account/Balance Endpoints
/api/account-balance/[address].tsxAccountBalance/api/pending-stake/[address].tsxPendingFeesAndStake/api/l1-delegator/[address].tsxL1Delegator2.2 ENS Endpoints
/api/ens-data/[address].tsxEnsIdentity2.3 Round/Protocol Endpoints
/api/current-round.tsxCurrentRoundInfo2.4 Performance/Score Endpoints
/api/score/[address].tsxPerformanceMetrics/api/score/index.tsxAllPerformanceMetrics2.5 Treasury Endpoints
/api/treasury/proposal/[proposalId]/state.tsxProposalState/api/treasury/votes/[address]/index.tsxVotingPower/api/treasury/votes/[address]/registered.tsxRegisteredToVote/api/treasury/proposal/[proposalId]/votes/[address].tsxProposalVotingPower2.6 Pipeline/Region Endpoints
/api/pipelines/index.tsxAvailablePipelines/api/regions/index.tsRegions2.7 Usage/Chart Endpoints
/api/usage.tsxHomeChartDataDayDataSchemafor external API responseHomeChartDatareturn type/api/upload-ipfs.tsxAddIpfs3. External API Response Validation
These endpoints fetch data from external APIs and should validate responses before using them.
3.1 Metrics/AI Server Responses
/api/score/[address].tsxNEXT_PUBLIC_AI_METRICS_SERVER_URL/api/top_ai_score→ScoreResponseNEXT_PUBLIC_METRICS_SERVER_URL/api/aggregated_stats→MetricsResponsePriceResponseawait response.json()/api/score/index.tsx/api/pipelines/index.tsxNEXT_PUBLIC_AI_METRICS_SERVER_URL/api/pipelinesawait response.json()AvailablePipelinesSchema(defined in section 2.6)/api/regions/index.tsNEXT_PUBLIC_METRICS_SERVER_URL/api/regionsNEXT_PUBLIC_AI_METRICS_SERVER_URL/api/regionsRegionsSchema(defined in section 2.6)3.2 Livepeer.com API
/api/usage.tsxhttps://livepeer.com/data/usage/query/totalDayDataSchemawithsafeParse()3.3 Pinata IPFS API
/api/upload-ipfs.tsxhttps://api.pinata.cloud/pinning/pinJSONToIPFSawait fetchResult.json()3.4 ENS Provider Responses
lib/api/ens.ts→getEnsForAddress()4. SSR/Client-Side API Calls
4.1 Server-Side Rendering
lib/api/ssr.ts→getEnsIdentity()/api/ens-data/${address}await response.json()EnsIdentitySchemato validate the response5. Recommended Implementation Strategy
Phase 1: Shared Schemas
lib/api/schemas/directoryaddress.ts- Address validationcommon.ts- Common types (strings, numbers, etc.)ens.ts- ENS-related schemastreasury.ts- Treasury/proposal schemasperformance.ts- Performance metrics schemasPhase 2: Input Validation
Phase 3: Output Validation
Phase 4: External API Validation
safeParse()to handle validation errors gracefullyExample Implementation Pattern
Summary
Total Opportunities:
Priority:
Estimated Impact:
Describe alternatives you've considered
No response
Additional context
No response