{"openapi":"3.1.0","info":{"title":"kiwiSMS API","version":"1.0.0","description":"REST API for SMS OTP number rental.\n\nCreate an API key in the dashboard (`/dashboard/api-keys`).\n\nHuman-readable docs: `/docs`.\n\nMachine-readable spec: `/openapi.json`."},"servers":[{"url":"https://kiwisms.com","description":"Current deployment"}],"tags":[{"name":"Account","description":"Wallet balance"},{"name":"Catalog","description":"Services and pricing"},{"name":"Rentals","description":"Rent, poll, cancel, complete, renew"}],"security":[{"ApiKeyHeader":[]},{"BearerAuth":[]}],"components":{"securitySchemes":{"ApiKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key","description":"API key format: `lsms_{prefix}_{secret}`"},"BearerAuth":{"type":"http","scheme":"bearer","description":"Same API key as Bearer token"}},"parameters":{"CountryQuery":{"name":"country","in":"query","description":"DaisySMS numeric country code","schema":{"type":"string","enum":["187","16","36","43","78","48"],"default":"187"}},"CountryCodeQuery":{"name":"country_code","in":"query","description":"Alias for `country`","schema":{"type":"string","enum":["187","16","36","43","78","48"]}},"SyncQuery":{"name":"sync","in":"query","description":"Set to `0` to skip provider SMS sync (faster polling)","schema":{"type":"string","enum":["0","1"],"default":"1"}},"RentalId":{"name":"id","in":"path","required":true,"schema":{"type":"string"}},"IdempotencyKey":{"name":"Idempotency-Key","in":"header","description":"Optional UUID for safe retries on POST (max 256 chars, 24h TTL)","schema":{"type":"string","maxLength":256}}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"code":{"type":"string"}}},"Balance":{"type":"object","required":["balance","currency"],"properties":{"balance":{"type":"number","description":"USD wallet balance"},"currency":{"type":"string","const":"USD"}}},"ServiceItem":{"type":"object","required":["service","name","price","count","multiSms"],"properties":{"service":{"type":"string","description":"Service code (e.g. go)"},"name":{"type":"string"},"price":{"type":"string","description":"USD price as string"},"count":{"type":"integer","description":"Available stock"},"multiSms":{"type":"boolean"}}},"ServicesResponse":{"type":"object","required":["countryCode","catalogMode","count","services"],"properties":{"countryCode":{"type":"string"},"catalogMode":{"type":"boolean"},"count":{"type":"integer"},"services":{"type":"array","items":{"$ref":"#/components/schemas/ServiceItem"}}}},"Rental":{"type":"object","required":["id","externalId","phone","service","serviceName","status","price","codes","otpCode","expiresAt","createdAt","active","secondsRemaining"],"properties":{"id":{"type":"string"},"externalId":{"type":["string","null"]},"phone":{"type":"string"},"service":{"type":"string"},"serviceName":{"type":"string"},"status":{"type":"string","enum":["WAITING","RECEIVED","COMPLETED","CANCELLED","EXPIRED"]},"price":{"type":"number"},"codes":{"type":"array","items":{"type":"string"}},"otpCode":{"type":["string","null"]},"expiresAt":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"},"active":{"type":"boolean"},"secondsRemaining":{"type":"integer"}}},"RentalCreated":{"type":"object","required":["id","externalId","phone","service","serviceName","price","status","expiresAt"],"properties":{"id":{"type":"string"},"externalId":{"type":["string","null"]},"phone":{"type":"string"},"service":{"type":"string"},"serviceName":{"type":"string"},"price":{"type":"number"},"status":{"type":"string","const":"WAITING"},"expiresAt":{"type":"string","format":"date-time"},"asleep":{"type":"boolean"}}},"RentRequest":{"type":"object","required":["service"],"properties":{"service":{"type":"string","minLength":1},"max_price":{"oneOf":[{"type":"string"},{"type":"number"}],"description":"Maximum price willing to pay (USD)"},"areas":{"type":"string","description":"Comma-separated area codes"},"carrier":{"type":"string","enum":["tmo","vz","att"]},"country":{"type":"string","enum":["187","16","36","43","78","48"]},"country_code":{"type":"string","enum":["187","16","36","43","78","48"]}}},"RentalListResponse":{"type":"object","required":["rentals","count"],"properties":{"rentals":{"type":"array","items":{"$ref":"#/components/schemas/Rental"}},"count":{"type":"integer"}}},"RentalResponse":{"type":"object","required":["rental"],"properties":{"rental":{"$ref":"#/components/schemas/Rental"}}},"RentResponse":{"type":"object","required":["rental"],"properties":{"rental":{"$ref":"#/components/schemas/RentalCreated"}}},"RentalActionResponse":{"type":"object","required":["success","rentalId","status"],"properties":{"success":{"type":"boolean","const":true},"rentalId":{"type":"string"},"status":{"type":"string","enum":["CANCELLED","COMPLETED"]}}},"PublicPricingItem":{"type":"object","required":["code","name","price"],"properties":{"code":{"type":"string"},"name":{"type":"string"},"price":{"type":"string"}}},"PublicPricingResponse":{"type":"object","required":["services"],"properties":{"services":{"type":"array","items":{"$ref":"#/components/schemas/PublicPricingItem"}}}},"OtpWebhookPayload":{"type":"object","required":["event","rentalId","serviceCode","serviceName","phone","otpCode","smsText","receivedAt"],"properties":{"event":{"type":"string","const":"sms.received"},"rentalId":{"type":"string"},"serviceCode":{"type":"string"},"serviceName":{"type":"string"},"phone":{"type":"string"},"otpCode":{"type":"string"},"smsText":{"type":"string"},"receivedAt":{"type":"string","format":"date-time"}},"description":"Outbound webhook POST (configure URL on account or API key). Signed with `X-Webhook-Signature` (HMAC-SHA256 of body)."}},"responses":{"Unauthorized":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"Account suspended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/api/v1/balance":{"get":{"operationId":"getBalance","tags":["Account"],"summary":"Wallet balance","responses":{"200":{"description":"USD balance","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Balance"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/services":{"get":{"operationId":"listServices","tags":["Catalog"],"summary":"Services with stock and prices","parameters":[{"$ref":"#/components/parameters/CountryQuery"},{"$ref":"#/components/parameters/CountryCodeQuery"}],"responses":{"200":{"description":"Service catalog","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServicesResponse"}}}},"400":{"description":"Unsupported country","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Unsupported country code","code":"INVALID_COUNTRY"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"description":"Upstream provider error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Failed to fetch services","code":"PROVIDER_ERROR"}}}}}}},"/api/v1/rentals":{"get":{"operationId":"listRentals","tags":["Rentals"],"summary":"List active rentals","parameters":[{"$ref":"#/components/parameters/SyncQuery"}],"responses":{"200":{"description":"Active rentals (WAITING / RECEIVED)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RentalListResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"operationId":"createRental","tags":["Rentals"],"summary":"Rent a number","description":"Charges wallet for a ~20 minute session. Send `Idempotency-Key` for safe retries.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RentRequest"}}}},"responses":{"201":{"description":"Rental created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RentResponse"}}}},"400":{"description":"Invalid JSON or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"Insufficient balance","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Insufficient balance","code":"INSUFFICIENT_BALANCE"}}}},"404":{"description":"Service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Service unavailable","code":"SERVICE_UNAVAILABLE"}}}},"409":{"description":"Idempotency conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Idempotency key reused with different body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Idempotency-Key was already used with a different request body","code":"IDEMPOTENCY_KEY_REUSED"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/rentals/{id}":{"get":{"operationId":"getRental","tags":["Rentals"],"summary":"Poll rental status and OTP codes","parameters":[{"$ref":"#/components/parameters/RentalId"},{"$ref":"#/components/parameters/SyncQuery"}],"responses":{"200":{"description":"Rental details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RentalResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Rental not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Rental not found","code":"RENTAL_NOT_FOUND"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/rentals/{id}/cancel":{"post":{"operationId":"cancelRental","tags":["Rentals"],"summary":"Cancel WAITING rental","description":"Refunds wallet when cancellation succeeds.","parameters":[{"$ref":"#/components/parameters/RentalId"}],"responses":{"200":{"description":"Cancelled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RentalActionResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Not found or not cancellable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Rental not found or cannot be cancelled (only WAITING rentals)","code":"CANCEL_NOT_ALLOWED"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"description":"Provider cancellation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Could not cancel rental on provider","code":"PROVIDER_ERROR"}}}}}}},"/api/v1/rentals/{id}/done":{"post":{"operationId":"completeRental","tags":["Rentals"],"summary":"Mark rental completed","description":"Releases the number on the upstream provider.","parameters":[{"$ref":"#/components/parameters/RentalId"}],"responses":{"200":{"description":"Completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RentalActionResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Rental not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Rental not found","code":"RENTAL_NOT_FOUND"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/rentals/{id}/renew":{"post":{"operationId":"renewRental","tags":["Rentals"],"summary":"Renew same phone number","description":"Starts a new ~20 minute session for the same number. Supports `Idempotency-Key`.","parameters":[{"$ref":"#/components/parameters/RentalId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"Renewed (or idempotent replay)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RentResponse"}}}},"201":{"description":"New rental session created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RentResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"Insufficient balance","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Insufficient balance","code":"INSUFFICIENT_BALANCE"}}}},"404":{"description":"Rental not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Rental not found","code":"NOT_FOUND"}}}},"409":{"description":"Idempotency conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Idempotency key reused with different body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/public/pricing":{"get":{"operationId":"getPublicPricing","tags":["Catalog"],"summary":"Public pricing snapshot (no auth)","security":[],"responses":{"200":{"description":"Top services for landing page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicPricingResponse"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}}},"x-webhooks":{"smsReceived":{"post":{"summary":"OTP received (outbound to your URL)","description":"Configured on user account or API key. Header `X-Webhook-Signature` is HMAC-SHA256 hex of the raw JSON body using your webhook secret.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OtpWebhookPayload"}}}},"responses":{"2XX":{"description":"Your server acknowledged the webhook"}}}}}}