{"openapi":"3.1.0","info":{"title":"Tenalet Kit API","version":"1.0.0","description":"Tenalet Kit lets partners embed tenant screening into their platforms.\nCreate tolets, collect applications via an embedded iframe, and access\nscreening reports — all through a single API key.\n\n## Authentication\n\nAll requests require an `X-API-Key` header with your partner API key.\nEach partner receives two keys: a **live** key and a **test** key.\n\n```bash\n# Live\ncurl -H \"X-API-Key: tnlt_pk_live_...\" https://api.tenalet.com/api/v1/partner/tolets\n\n# Test (sandbox)\ncurl -H \"X-API-Key: tnlt_pk_test_...\" https://api.tenalet.com/api/v1/partner/tolets\n```\n\n## Sandbox vs Live\n\nSandbox and live environments share the same API base URL. The **API key**\ndetermines the mode:\n\n| Key prefix | Mode | Description |\n|------------|------|-------------|\n| `tnlt_pk_live_` | Live | Real data and billing. |\n| `tnlt_pk_test_` | Test | Sandbox data, no billing. |\n\n- Tolets created with a **test** key are marked as sandbox (`isSandbox: true`).\n- Applications and users inherit sandbox status from their tolet.\n- A **test** key can only access sandbox resources; a **live** key can only\n  access live resources. Cross-environment requests return `403 Forbidden`.\n- `GET /tolets` automatically filters results by the key's mode.\n\n| | API Base URL | App URL (Embed SDK) |\n|---|---|---|\n| **All environments** | `https://api.tenalet.com/api/v1/partner` | `https://app.tenalet.com` |\n\n## Embed SDK\n\nThe embed SDK (`embed.js`) renders the Tenalet screening widget inside an iframe\non your page:\n\n    <script src=\"https://app.tenalet.com/embed.js\"></script>\n    <div id=\"tenalet-app\"></div>\n\nAfter creating an application via the API, start the embed:\n\n```js\nconst embed = Tenalet.startApplication({\n  applicationId: response.applicationId,\n  token: response.token,\n  refreshToken: response.refreshToken,\n  containerId: 'tenalet-app',\n  redirectUrl: '/success',\n  onLoaded: () => {},\n  onAuthenticated: (data) => {},\n  onApplicationStarted: (data) => {},\n  onApplicationSubmitted: (data) => {},\n  onError: (data) => {},\n});\n```\n\n![Tenalet Kit embed widget](https://raw.githubusercontent.com/tenalet/kit-demo/main/docs/embed-preview.png)\n\n### Token Lifecycle\n\nThe access token is short-lived. The embed SDK automatically uses the\n`refreshToken` to renew the session silently. Always pass both `token` and\n`refreshToken` to `startApplication()`.\n\n## Demo\n\nA working reference implementation is available at\n[github.com/tenalet/kit-demo](https://github.com/tenalet/kit-demo).\nIt demonstrates tolet creation, embedded screening, report access,\nand webhook handling with a simple Express server and static frontend.\n","contact":{"name":"Tenalet Engineering","email":"engineering@tenalet.com"},"license":{"name":"MIT","identifier":"MIT"}},"servers":[{"url":"https://api.tenalet.com/api/v1/partner","description":"Production (use test or live API key to select mode)"}],"security":[{"apiKey":[]}],"tags":[{"name":"Tolets","description":"Create and manage properties (tolets) for tenant screening."},{"name":"Applications","description":"Create screening applications and manage embed credentials."},{"name":"Reports","description":"Access screening reports for completed applications."}],"paths":{"/tolets":{"post":{"operationId":"createTolet","summary":"Create a tolet","description":"Create a property listing for tenant screening.\nThe tolet inherits sandbox status from the API key mode:\ntest keys create sandbox tolets, live keys create live tolets.\n","tags":["Tolets"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateToletRequest"},"example":{"property":{"address":{"street":"12 Adeola Odeku St","city":"Victoria Island","state":"Lagos"},"role":"landlord"},"requirements":{"modules":["rentalApplication","incomeVerification","creditHistoryAndScore"]},"note":"2-bedroom flat, 3rd floor","rent":{"amount":1200000,"frequency":"yearly"},"isAcceptingApplications":true}}}},"responses":{"201":{"description":"Tolet created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tolet"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}},"get":{"operationId":"listTolets","summary":"List tolets","description":"List your tolets with pagination.\nResults are filtered by API key mode: test keys return only sandbox tolets,\nlive keys return only live tolets.\n","tags":["Tolets"],"parameters":[{"$ref":"#/components/parameters/Page"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Sort"}],"responses":{"200":{"description":"Paginated list of tolets.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToletListResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/tolets/{toletId}":{"get":{"operationId":"getTolet","summary":"Get a tolet","description":"Get a single tolet's details.","tags":["Tolets"],"parameters":[{"$ref":"#/components/parameters/ToletId"}],"responses":{"200":{"description":"Tolet details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tolet"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"operationId":"updateTolet","summary":"Update a tolet","description":"Partial update of a tolet's properties.","tags":["Tolets"],"parameters":[{"$ref":"#/components/parameters/ToletId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateToletRequest"}}}},"responses":{"200":{"description":"Tolet updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tolet"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/tolets/{toletId}/applications":{"post":{"operationId":"createApplication","summary":"Create an application","description":"Create a screening application for a tolet and receive embed credentials.\nThis is the main endpoint for the embed flow. Pass the returned `token`,\n`refreshToken`, and `applicationId` to `Tenalet.startApplication()`.\n","tags":["Applications"],"parameters":[{"$ref":"#/components/parameters/ToletId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApplicationRequest"},"example":{"externalUserId":"usr_abc123","firstName":"Kemi","lastName":"Adebayo","phone":"+2348012345678"}}}},"responses":{"201":{"description":"Application created with embed credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbedCredentials"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"get":{"operationId":"listApplications","summary":"List applications for a tolet","description":"List screening applications for a tolet with pagination.","tags":["Applications"],"parameters":[{"$ref":"#/components/parameters/ToletId"},{"$ref":"#/components/parameters/Page"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Sort"}],"responses":{"200":{"description":"Paginated list of applications.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplicationListResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/applications/{applicationId}":{"get":{"operationId":"getApplication","summary":"Get an application","description":"Get a single application's details including status, timestamps, and applicant info.","tags":["Applications"],"parameters":[{"$ref":"#/components/parameters/ApplicationId"}],"responses":{"200":{"description":"Application details.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Application"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/applications/{applicationId}/embed-token":{"post":{"operationId":"createEmbedToken","summary":"Generate embed token","description":"Generate fresh embed tokens for an existing **draft** application.\nUse this to resume an application that was started but not yet submitted.\n\nReturns `400` if the application has already been submitted.\n","tags":["Applications"],"parameters":[{"$ref":"#/components/parameters/ApplicationId"}],"responses":{"200":{"description":"Fresh embed credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbedCredentials"}}}},"400":{"description":"Application has already been submitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/applications/{applicationId}/reports":{"get":{"operationId":"listReports","summary":"List reports","description":"List available screening reports for a completed application.","tags":["Reports"],"parameters":[{"$ref":"#/components/parameters/ApplicationId"}],"responses":{"200":{"description":"List of available reports with optional generation progress.","content":{"application/json":{"schema":{"type":"object","properties":{"reports":{"type":"array","items":{"$ref":"#/components/schemas/Report"}},"reportGeneration":{"$ref":"#/components/schemas/ReportGenerationProgress"}},"required":["reports"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/applications/{applicationId}/reports/{reportType}":{"get":{"operationId":"getReportUrl","summary":"Get report download URL","description":"Get an ephemeral URL (60-second expiry) to download a screening report.\n","tags":["Reports"],"parameters":[{"$ref":"#/components/parameters/ApplicationId"},{"name":"reportType","in":"path","required":true,"description":"The type of report to download.","schema":{"$ref":"#/components/schemas/ReportType"}}],"responses":{"200":{"description":"Ephemeral download URL.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportUrl"}}}},"202":{"description":"Report is still being generated. Poll again for progress updates.","content":{"application/json":{"schema":{"type":"object","properties":{"reportGeneration":{"$ref":"#/components/schemas/ReportGenerationProgress"}},"required":["reportGeneration"]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}}},"webhooks":{"application.submitted":{"post":{"operationId":"onApplicationSubmitted","summary":"application.submitted","description":"Fired when an applicant completes and submits their screening.","tags":["Webhooks"],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplicationSubmittedEvent"}}}},"responses":{"200":{"description":"Acknowledge receipt."}},"x-webhook-headers":{"X-Tenalet-Signature":{"description":"HMAC-SHA256 hex digest of the raw request body using your webhook secret."},"X-Tenalet-Event":{"description":"The event type string."}}}},"application.reports_generated":{"post":{"operationId":"onReportsGenerated","summary":"application.reports_generated","description":"Fired when screening reports are available for download.","tags":["Webhooks"],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportsGeneratedEvent"}}}},"responses":{"200":{"description":"Acknowledge receipt."}}}},"payment.completed":{"post":{"operationId":"onPaymentCompleted","summary":"payment.completed","description":"Fired when a screening payment is successfully processed.","tags":["Webhooks"],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentCompletedEvent"}}}},"responses":{"200":{"description":"Acknowledge receipt."}}}}},"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Your partner API key."}},"parameters":{"ToletId":{"name":"toletId","in":"path","required":true,"description":"The tolet UUID.","schema":{"type":"string","format":"uuid"}},"ApplicationId":{"name":"applicationId","in":"path","required":true,"description":"The application UUID.","schema":{"type":"string","format":"uuid"}},"Page":{"name":"page","in":"query","required":false,"description":"Page number (1-indexed).","schema":{"type":"integer","minimum":1,"default":1}},"Limit":{"name":"limit","in":"query","required":false,"description":"Items per page.","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},"Sort":{"name":"sort","in":"query","required":false,"description":"Sort order by creation date.","schema":{"type":"string","enum":["ASC","DESC"],"default":"DESC"}}},"responses":{"BadRequest":{"description":"Invalid request body or parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"API key mode does not match the resource's environment (e.g. live key on sandbox tolet).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Address":{"type":"object","required":["street","city","state"],"properties":{"unit":{"type":"string","maxLength":20,"description":"Unit or apartment number.","example":"Apt 4B"},"street":{"type":"string","minLength":3,"maxLength":80,"description":"Street address.","example":"12 Adeola Odeku St"},"city":{"type":"string","minLength":2,"maxLength":50,"example":"Victoria Island"},"state":{"type":"string","minLength":3,"maxLength":30,"example":"Lagos"}}},"Landlord":{"type":"object","required":["firstName","lastName"],"properties":{"firstName":{"type":"string","maxLength":50,"example":"John"},"lastName":{"type":"string","maxLength":50,"example":"Doe"},"email":{"type":"string","format":"email"}}},"PropertyUserRole":{"type":"string","description":"Your relationship to the property.","enum":["landlord","tenant_agent","listing_agent","property_manager"]},"Property":{"type":"object","required":["address","role"],"properties":{"address":{"$ref":"#/components/schemas/Address"},"role":{"$ref":"#/components/schemas/PropertyUserRole"},"landlord":{"$ref":"#/components/schemas/Landlord"}}},"ScreeningModule":{"type":"string","description":"Available screening modules. Note: `incomeVerification` requires\n`creditHistoryAndScore` to also be selected.\n","enum":["rentalApplication","incomeVerification","creditHistoryAndScore"]},"Requirements":{"type":"object","required":["modules"],"properties":{"modules":{"type":"array","items":{"$ref":"#/components/schemas/ScreeningModule"},"minItems":1,"description":"Screening modules to require for this property."}}},"Rent":{"type":"object","required":["amount"],"properties":{"amount":{"type":"number","minimum":5000,"maximum":100000000,"description":"Rent amount in naira (not kobo).","example":1200000},"frequency":{"type":"string","enum":["yearly","monthly"],"default":"yearly","description":"Payment frequency."},"currency":{"type":"string","enum":["NGN"],"default":"NGN","description":"Currency code. Only NGN is supported."}}},"CreateToletRequest":{"type":"object","required":["property"],"properties":{"property":{"$ref":"#/components/schemas/Property"},"requirements":{"$ref":"#/components/schemas/Requirements"},"note":{"type":"string","maxLength":500,"description":"Internal note for this property.","example":"2-bedroom flat, 3rd floor"},"rent":{"oneOf":[{"$ref":"#/components/schemas/Rent"},{"type":"null"}],"description":"Optional rent information. Send `null` to clear."},"isAcceptingApplications":{"type":"boolean","description":"Whether the property is accepting new applications.","default":true}}},"UpdateToletRequest":{"type":"object","properties":{"property":{"$ref":"#/components/schemas/Property"},"requirements":{"$ref":"#/components/schemas/Requirements"},"note":{"type":"string","maxLength":500},"rent":{"oneOf":[{"$ref":"#/components/schemas/Rent"},{"type":"null"}],"description":"Rent information. Send `null` to clear."},"isAcceptingApplications":{"type":"boolean"}}},"Tolet":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"displayName":{"type":"string","description":"Human-readable name derived from the property address."},"property":{"$ref":"#/components/schemas/Property"},"requirements":{"$ref":"#/components/schemas/Requirements"},"linkCode":{"type":"string","description":"Unique code used in the property's shareable link."},"shortUrl":{"type":"string","description":"Short URL for sharing the property listing."},"isAcceptingApplications":{"type":"boolean"},"isActive":{"type":"boolean","description":"Whether the tolet is active."},"isSandbox":{"type":"boolean","description":"Whether this is a sandbox (test) tolet. Determined by the API key used at creation."},"note":{"type":"string"},"rent":{"oneOf":[{"$ref":"#/components/schemas/Rent"},{"type":"null"}],"description":"Rent information if set by the partner."},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"ToletListResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Tolet"}},"meta":{"$ref":"#/components/schemas/PaginationMeta"}}},"CreateApplicationRequest":{"type":"object","required":["externalUserId"],"properties":{"externalUserId":{"type":"string","maxLength":100,"description":"Your unique identifier for the applicant. Alphanumeric characters,\ndots, hyphens, and underscores are allowed.\n","pattern":"^[a-zA-Z0-9._-]+$","example":"usr_abc123"},"firstName":{"type":"string","maxLength":100,"description":"Pre-fills the application form.","example":"Kemi"},"lastName":{"type":"string","maxLength":100,"description":"Pre-fills the application form.","example":"Adebayo"},"phone":{"type":"string","maxLength":20,"description":"Pre-fills the application form.","example":"+2348012345678"}}},"EmbedCredentials":{"type":"object","description":"Credentials to initialise the embed SDK.","properties":{"applicationId":{"type":"string","format":"uuid","description":"UUID of the created application."},"token":{"type":"string","description":"Short-lived JWT access token for the embed iframe."},"tokenType":{"type":"string","description":"Always `Bearer`.","enum":["Bearer"]},"expiresIn":{"type":"integer","description":"Access token lifetime in seconds.","example":900},"refreshToken":{"type":"string","description":"Long-lived refresh token for silent session renewal."},"refreshExpiresIn":{"type":"integer","description":"Refresh token lifetime in seconds.","example":604800},"embedUrl":{"type":"string","format":"uri","description":"Full URL to load in the embed iframe.","example":"https://app.tenalet.com/embed/apply/550e8400-e29b-41d4-a716-446655440000"}}},"ApplicationStatus":{"type":"string","enum":["draft","submitted","pending_modules","approved","rejected"]},"Application":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"toletId":{"type":"string","format":"uuid"},"applicantFullName":{"type":"string","description":"Full name of the applicant."},"email":{"type":"string","format":"email"},"status":{"$ref":"#/components/schemas/ApplicationStatus"},"submittedAt":{"type":["string","null"],"format":"date-time"},"approvedAt":{"type":["string","null"],"format":"date-time"},"rejectedAt":{"type":["string","null"],"format":"date-time"},"createdAt":{"type":"string","format":"date-time"}}},"ApplicationListResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Application"}},"meta":{"$ref":"#/components/schemas/PaginationMeta"}}},"ReportType":{"type":"string","description":"Type of screening report.","enum":["income","credit","application","full"]},"Report":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/ReportType"},"name":{"type":"string","description":"Human-readable report name."},"available":{"type":"boolean","description":"Whether the report is ready for download."},"generatedAt":{"type":["string","null"],"format":"date-time","description":"When the report was generated."}}},"ReportUrl":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"Ephemeral download URL. Expires after 60 seconds."},"expiresAt":{"type":"string","format":"date-time","description":"Exact expiration timestamp of the URL."}}},"ReportGenerationProgress":{"type":"object","description":"Report generation progress. Present when reports are still being generated.","properties":{"status":{"type":"string","enum":["queued","processing","failed"],"description":"Current generation status."},"progress":{"type":["integer","null"],"minimum":0,"maximum":100,"description":"Progress percentage (0-100)."},"phase":{"type":"string","description":"Human-readable phase description.","example":"Analyzing income, generating credit report"},"attemptsMade":{"type":"integer","description":"Number of retry attempts made."},"failureReason":{"type":"string","description":"User-facing failure message (only when status is failed)."},"enqueuedAt":{"type":"string","format":"date-time","description":"When the generation job was enqueued."}},"required":["status","progress"]},"ApplicationSubmittedEvent":{"type":"object","properties":{"event":{"type":"string","enum":["application.submitted"]},"data":{"type":"object","properties":{"applicationId":{"type":"string","format":"uuid"},"toletId":{"type":"string","format":"uuid"},"applicantId":{"type":"string","format":"uuid"},"submittedAt":{"type":"string","format":"date-time"}}},"timestamp":{"type":"string","format":"date-time"}}},"ReportsGeneratedEvent":{"type":"object","properties":{"event":{"type":"string","enum":["application.reports_generated"]},"data":{"type":"object","properties":{"applicationId":{"type":"string","format":"uuid"},"toletId":{"type":"string","format":"uuid"},"availableReports":{"type":"array","items":{"$ref":"#/components/schemas/ReportType"},"description":"Report types now available for download.","example":["application","full"]}}},"timestamp":{"type":"string","format":"date-time"}}},"PaymentCompletedEvent":{"type":"object","properties":{"event":{"type":"string","enum":["payment.completed"]},"data":{"type":"object","properties":{"applicationId":{"type":"string","format":"uuid"},"toletId":{"type":"string","format":"uuid"},"reference":{"type":"string","description":"Payment reference from the payment provider."},"amount":{"type":"number","description":"Amount paid in kobo.","example":500000},"modules":{"type":"object","description":"Map of screening modules included in this payment."},"provider":{"type":"string","description":"Payment provider used.","example":"paystack"}}},"timestamp":{"type":"string","format":"date-time"}}},"PaginationMeta":{"type":"object","properties":{"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"},"totalPages":{"type":"integer"}}},"Error":{"type":"object","properties":{"error":{"type":"string","description":"Error type.","example":"Bad Request"},"message":{"type":"string","description":"Human-readable error description.","example":"Email is already registered."},"statusCode":{"type":"integer","example":400}}}}}}