openapi: 3.0.3 info: title: Specto LCO API version: "1.0" description: | Activate, renew and advance-renew Watcho subscriptions for your Specto LCO account from your own software. Your software communicates only with the Specto API — never with Watcho. Specto manages the LCO wallet, pricing, subscriber records, Watcho provider communication, the transaction ledger, expiry and MSO commission. **Authentication:** every request carries `X-API-Key`, `X-Timestamp`, `X-Nonce` and `X-Signature`. The signature is lower-case hex HMAC-SHA256(api_secret, string_to_sign), where string_to_sign is `METHOD\nPATH\nCANONICAL_QUERY\nTIMESTAMP\nNONCE\nhex(sha256(body))`. Full specification and a test vector: https://api.spectotv.com/docs/#signing. Requests are accepted only from whitelisted IP addresses. contact: name: Specto API support servers: - url: https://api.spectotv.com/api/v1 security: - ApiKey: [] Timestamp: [] Nonce: [] Signature: [] tags: - name: Operations description: Wallet-debiting subscription operations (require Idempotency-Key) - name: Lookups description: Read-only endpoints paths: /activate: post: tags: [Operations] summary: Create a subscriber and activate a Watcho plan description: Debits the LCO wallet by the catalogue price. Permission "Activate". parameters: [ { $ref: '#/components/parameters/IdempotencyKey' } ] requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/ActivateRequest' } example: { full_name: Ravi Kumar, mobile: "9876543210", email: ravi@example.com, plan_id: "171839" } responses: '200': { $ref: '#/components/responses/OperationOk' } '202': { $ref: '#/components/responses/PendingReview' } '402': { $ref: '#/components/responses/InsufficientBalance' } '409': { $ref: '#/components/responses/Error' } '422': { $ref: '#/components/responses/Error' } '502': { $ref: '#/components/responses/ProviderError' } default: { $ref: '#/components/responses/Error' } /renew: post: tags: [Operations] summary: Renew one of your subscribers description: New expiry = current expiry (or now if expired) + duration × 30 days. Permission "Renew". parameters: [ { $ref: '#/components/parameters/IdempotencyKey' } ] requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/RenewRequest' } example: { subscriber_id: "482915", plan_id: "171850" } responses: '200': { $ref: '#/components/responses/OperationOk' } '202': { $ref: '#/components/responses/PendingReview' } '402': { $ref: '#/components/responses/InsufficientBalance' } '404': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } '502': { $ref: '#/components/responses/ProviderError' } default: { $ref: '#/components/responses/Error' } /advance-renew: post: tags: [Operations] summary: Schedule the next cycle before the current one ends description: Start date = expiry + 1 day (capped at today + 29; today if expired). Same active plan → 409 PLAN_ALREADY_ACTIVE. Permission "Advance Renew". parameters: [ { $ref: '#/components/parameters/IdempotencyKey' } ] requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/RenewRequest' } responses: '200': { $ref: '#/components/responses/OperationOk' } '202': { $ref: '#/components/responses/PendingReview' } '402': { $ref: '#/components/responses/InsufficientBalance' } '404': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } '502': { $ref: '#/components/responses/ProviderError' } default: { $ref: '#/components/responses/Error' } /balance: get: tags: [Lookups] summary: Wallet balance (read-only) responses: '200': description: Balance content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object properties: data: type: object properties: lco_id: { type: integer } wallet_balance: { type: number, format: double } currency: { type: string, example: INR } low_balance: { type: boolean } as_of: { type: string, format: date-time } note: { type: string } default: { $ref: '#/components/responses/Error' } /plans: get: tags: [Lookups] summary: Plan catalogue with prices responses: '200': description: Plans content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object properties: data: type: object properties: provider: { type: string, example: watcho } currency: { type: string, example: INR } plans: type: array items: { $ref: '#/components/schemas/Plan' } '503': { $ref: '#/components/responses/Error' } default: { $ref: '#/components/responses/Error' } /subscription: get: tags: [Lookups] summary: Look up one of your subscribers parameters: - { name: subscriber_id, in: query, schema: { type: string }, description: Specto subscriber ID } - { name: mobile, in: query, schema: { type: string, pattern: '^[0-9]{10}$' }, description: Used when subscriber_id is not given } responses: '200': description: Subscriber content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object properties: data: { $ref: '#/components/schemas/Subscription' } '404': { $ref: '#/components/responses/Error' } default: { $ref: '#/components/responses/Error' } /transaction: get: tags: [Lookups] summary: Look up one of your operations parameters: - { name: transaction_id, in: query, schema: { type: string, example: SPX260925A1B2C3D4E5 } } - { name: idempotency_key, in: query, schema: { type: string } } responses: '200': description: Transaction content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object properties: data: allOf: - $ref: '#/components/schemas/OperationResult' - type: object properties: result_code: { type: string } result_message: { type: string } '404': { $ref: '#/components/responses/Error' } default: { $ref: '#/components/responses/Error' } components: securitySchemes: ApiKey: { type: apiKey, in: header, name: X-API-Key, description: "spk_ + 32 hex characters" } Timestamp: { type: apiKey, in: header, name: X-Timestamp, description: "Unix seconds; within ±300 s of server time" } Nonce: { type: apiKey, in: header, name: X-Nonce, description: "16–64 chars [A-Za-z0-9_-], unique per request" } Signature: { type: apiKey, in: header, name: X-Signature, description: "hex HMAC-SHA256 — see the documentation" } parameters: IdempotencyKey: name: Idempotency-Key in: header required: true schema: { type: string, pattern: '^[A-Za-z0-9_.:\-]{8,100}$' } description: One key per business operation; reuse it on retries — never charged twice. schemas: Envelope: type: object required: [success, request_id, code, message, data] properties: success: { type: boolean } request_id: { type: string, example: req_9ba40a67453c36ae4856 } code: { type: string, example: OK } message: { type: string } data: { type: object } ActivateRequest: type: object required: [full_name, mobile, plan_id] properties: full_name: { type: string, maxLength: 100 } mobile: { type: string, pattern: '^[0-9]{10}$' } plan_id: { type: string } email: { type: string, format: email, maxLength: 150 } password: { type: string, maxLength: 64, description: Generated and returned once if omitted } connection_type: { type: string, default: OTT + Live TV } address: { type: string, maxLength: 500 } subscriber_id: { type: string, pattern: '^[0-9]{6}$', description: Optional preferred ID } RenewRequest: type: object required: [plan_id] properties: subscriber_id: { type: string } mobile: { type: string, pattern: '^[0-9]{10}$', description: Used only if subscriber_id is empty } plan_id: { type: string } Plan: type: object properties: id: { type: string } name: { type: string } description: { type: string } price: { type: number, format: double } duration: { type: integer, description: Months (30 days each) } type: { type: string, enum: [standard, combo] } OperationResult: type: object properties: transaction_id: { type: string } idempotency_key: { type: string } operation: { type: string, enum: [activate, renew, advance_renew] } status: { type: string, enum: [success, failed, needs_review, processing] } channel: { type: string, enum: [api, web] } customer: type: object properties: subscriber_id: { type: string } name: { type: string } mobile: { type: string } password: { type: string, description: Activation only, returned once when generated } provider: { type: string, example: watcho } provider_transaction_id: { type: string, nullable: true } plan: type: object properties: id: { type: string } name: { type: string } duration_months: { type: integer } amount: { type: number, format: double } currency: { type: string, example: INR } expiry: type: object properties: previous: { type: string, nullable: true } new: { type: string, nullable: true } start_date: { type: string, nullable: true } wallet: type: object properties: before: { type: number } after: { type: number } charged: { type: number } refunded: { type: boolean } held: { type: boolean } warnings: { type: array, items: { type: string, enum: [COMBO_SECONDARY_FAILED] } } created_at: { type: string } completed_at: { type: string, nullable: true } Subscription: type: object properties: customer: type: object properties: subscriber_id: { type: string } name: { type: string } mobile: { type: string } email: { type: string } connection_type: { type: string } provider: { type: string } plan: { type: object, properties: { id: { type: string }, name: { type: string } } } status: { type: string } active: { type: boolean } expiry_date: { type: string, nullable: true } days_remaining: { type: integer } created_at: { type: string, nullable: true } renewed_at: { type: string, nullable: true } last_provider_transaction: { type: string, nullable: true } pending_operations: { type: array, items: { type: object } } responses: OperationOk: description: Completed (code OK) content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object properties: { data: { $ref: '#/components/schemas/OperationResult' } } PendingReview: description: Sent to Watcho, outcome not yet confirmed — amount held (code PENDING_REVIEW). Poll /transaction. content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object properties: { data: { $ref: '#/components/schemas/OperationResult' } } ProviderError: description: Watcho rejected the request; any reservation refunded (code PROVIDER_ERROR) content: application/json: schema: { $ref: '#/components/schemas/Envelope' } InsufficientBalance: description: Wallet zero or below price — Watcho not called, nothing charged content: application/json: schema: { $ref: '#/components/schemas/Envelope' } example: { success: false, request_id: req_…, code: INSUFFICIENT_BALANCE, message: Insufficient wallet balance, data: { wallet_balance: 50.0, required_amount: 99.0, shortfall: 49.0, currency: INR } } Error: description: Error — see `code` (full list in the documentation) content: application/json: schema: { $ref: '#/components/schemas/Envelope' }