Partner Keys
Partner Keys let an approved organization create and manage natural-person residency applications over REST. Each `pk-...` key belongs to one Partner Integration and can access only that integration's applications.
The actors have separate responsibilities: the partner manages application data, private proof uploads, payment, status, and submission. The authenticated applicant personally accepts the Agreement of Coexistence (AOC) and completes Veriff inside eProspera. Partners cannot submit a signature, start Veriff, receive a Veriff URL, or read Veriff documents or decisions through this API.
Base URL
| Environment | Origin |
|---|---|
| Production | https://portal.eprospera.com |
| Staging | https://staging-portal.eprospera.com |
Provisioning and key lifecycle
Partner integrations and keys are issued by eProspera administrators. They are not self-service.
- The raw key is displayed once. eProspera stores only its SHA-256 hash.
- Keys expire after 90 days.
- Issuing a replacement key shortens older active keys to a maximum seven-day overlap.
- A key can be revoked immediately. Integrations can be suspended or revoked independently.
- Authentication is rejected when the key is expired or revoked, the integration is not active, or its legal entity is dissolved.
- Redirect and checkout return URLs must use an HTTPS origin approved on the integration.
Send the key on every request:
Authorization: Bearer pk-...Scopes
| Scope | Capability |
|---|---|
partner:person.application.read | List and read this integration's applications and private uploads |
partner:person.application.create | Create applications and upload proof of address |
partner:person.application.update | Update Draft applications and upload proof of address |
partner:person.application.pay | Create checkout sessions and apply voucher payments |
partner:person.application.submit | Submit a completed application |
There is no API scope for signing the Agreement of Coexistence. Applicant consent always occurs on an eProspera-hosted page.
Endpoints
| Method | Path | Required scope |
|---|---|---|
POST | /api/v1/partner/residency_applications | partner:person.application.create |
GET | /api/v1/partner/residency_applications | partner:person.application.read |
GET | /api/v1/partner/residency_applications/{id} | partner:person.application.read |
PATCH | /api/v1/partner/residency_applications/{id} | partner:person.application.update |
POST | /api/v1/uploads/proof_of_address | create or update |
GET | /api/v1/uploads/proof_of_address/{uploadId} | read, create, or update |
POST | /api/v1/partner/residency_applications/{id}/checkout_session | partner:person.application.pay |
POST | /api/v1/partner/residency_applications/{id}/pay/voucher | partner:person.application.pay |
POST | /api/v1/partner/residency_applications/{id}/pay/coupon | partner:person.application.pay (deprecated alias) |
POST | /api/v1/partner/residency_applications/{id}/submit | partner:person.application.submit |
Cross-partner reads and mutations return 404 so the existence of another integration's resources is not disclosed.
Secure application flow
Partner and applicant responsibilities
| Stage | Partner through the Partner API | Applicant inside eProspera |
|---|---|---|
| Application | Creates the Draft and updates data using expectedVersion | — |
| Proof and payment | Uploads private proof of address and completes checkout or voucher payment | — |
| Account access | Reads applicantPortalAccess status only; never receives the claim token | Claims the account from the 24-hour email and logs in |
| AOC acceptance | Fetches and delivers the current agreementUrl; the partner cannot sign | Authenticates as the application owner and signs only on eProspera |
| Veriff | Reads verificationRequired and submitReady only; cannot initiate Veriff or view its data | Starts and completes Veriff in the authenticated portal |
| Submission and status | Submits when submitReady is true, then polls application status | — |
- Create a Draft with
POST /partner/residency_applicationsand a uniqueIdempotency-Key. eProspera sends the applicant a single-use account-claim link that expires after 24 hours. - Choose one proof path: upload a document using the new application ID, or send
applicationData.personalDetails.proofOfAddressSwornStatementinline on create/PATCH. - For the upload path only, attach the returned opaque
proofOfAddressUploadIdwithPATCH /partner/residency_applications/{id}and the latestexpectedVersion. The sworn-statement path skips upload and attachment. - Pay with a checkout session or voucher. Each payment request requires its own
Idempotency-Key. - Instruct the applicant to use the claim email and log in to eProspera. Fetch the application again after the final PATCH, then send that applicant the current
nextSteps.agreementUrl. - The agreement URL contains a 30-minute, single-use token. eProspera requires the applicant's Auth0 login and verifies that the logged-in subject owns the application. During login, the bearer is held server-side behind a short-lived, single-use opaque continuation rather than copied into login or
returnToURLs. A different user receives404, and admin impersonation is not accepted as applicant identity. - After acceptance, fetch the application again: acceptance consumes the link and increments
version. Use the refreshed version for any later PATCH. - The logged-in applicant starts and completes Veriff in the authenticated eProspera portal. The partner sees only
applicantPortalAccess,nextSteps.verificationRequired, andnextSteps.submitReady. - Submit the application after
nextSteps.submitReadybecomestrue.
The hosted agreement page selects the applicable AOC server-side. Acceptance records the agreement ID and version, SHA-256 hashes of its content and the legally relevant application data, the authenticated applicant's Auth0 subject, signer name, signature image, timestamp, IP address, user agent, and access-token ID. A legally relevant PATCH invalidates the current acceptance and returns a new agreementUrl; submission also rejects consent if application data changed through another path. Bearer-only acceptances without a matching authenticated signer are not valid for submission.
Submission requires current agreement acceptance, proof of address, a paid invoice, and approved identity verification. AOC acceptance and payment endpoints never submit the application; submission occurs only through /submit with partner:person.application.submit.
Create and update contracts
Create requests require Idempotency-Key:
curl -X POST https://portal.eprospera.com/api/v1/partner/residency_applications \
-H "Authorization: Bearer pk-REDACTED" \
-H "Idempotency-Key: application-ada-20260716" \
-H "Content-Type: application/json" \
-d '{
"applicationData": {
"residencyType": "e_resident",
"personalDetails": {
"givenName": "Ada",
"surname": "Lovelace",
"email": "applicant@example.com",
"dateOfBirth": "1990-12-10",
"sex": "female",
"countryOfBirth": "GB",
"citizenships": ["GB"],
"alternateNamesAttestation": false,
"criminalBackgroundAttestation": true,
"address": {
"line1": "123 Main St",
"city": "London",
"postalCode": "SW1A 1AA",
"country": "GB"
},
"phoneNumber": "+447700900123"
}
},
"redirectUrl": "https://partner.example/return"
}'An update sends the full application DTO plus the response's latest version:
{
"expectedVersion": 3,
"applicationData": {
"residencyType": "e_resident",
"personalDetails": {
"givenName": "Ada",
"surname": "Lovelace",
"email": "applicant@example.com",
"dateOfBirth": "1990-12-10",
"sex": "female",
"countryOfBirth": "GB",
"citizenships": ["GB"],
"alternateNamesAttestation": false,
"criminalBackgroundAttestation": true,
"proofOfAddressUploadId": "12345678-abcd-4abc-8abc-123456789012",
"address": {
"line1": "123 Main St",
"city": "London",
"postalCode": "SW1A 1AA",
"country": "GB"
},
"phoneNumber": "+447700900123"
}
}
}Once an invoice is paid, the residency type and applicant email are locked. A stale expectedVersion returns 409 with currentVersion.
Response fields
Application responses include:
| Field | Meaning |
|---|---|
version | Integer used as the next PATCH expectedVersion |
applicationData | Curated application data |
applicationData.personalDetails.proofOfAddressUploadId | Opaque private-upload ID, when an upload is attached |
applicantPortalAccess | Claim/login metadata; never contains credentials or a Veriff URL |
nextSteps.agreementUrl | Hosted consent URL while current acceptance is required |
nextSteps.proofOfAddressRequired | Whether proof is still missing |
nextSteps.paymentRequired | Whether payment is still required |
nextSteps.verificationRequired | Whether portal identity verification is still required |
nextSteps.verificationInstructions | Applicant-facing portal verification guidance, when required |
nextSteps.submitReady | Whether all submission prerequisites currently pass |
nextSteps.agreementUrl is ephemeral. Fetch the application after every PATCH and after AOC acceptance. Do not cache or reuse an older URL or version.
applicantPortalAccess.claimLinkSent and claimLinkSentAt describe whether the current mutation enqueued a claim email. GET requests do not send email and therefore return false and null for those fields.
Application lists use cursor pagination:
GET /api/v1/partner/residency_applications?limit=50&cursor=<application-id>limit defaults to 50 and cannot exceed 100. The response contains pagination.nextCursor, or null on the final page.
Idempotency
Application creation, checkout creation, and voucher payment require an Idempotency-Key containing 8–128 URL-safe characters. Records are retained for 24 hours per integration and route.
- An identical completed request replays the stored response with
Idempotency-Replayed: true. - Reusing a key with different input returns
409. - A duplicate received while the original is processing returns
409.
Rate limits
Limits are enforced by both API key and integration:
| Class | Per key | Per integration |
|---|---|---|
| Reads | 120/minute | 300/minute |
| Updates and submission | 60/minute | 150/minute |
| Application creation | 10/minute | 50/hour |
| Uploads | 20/minute | 100/hour |
| Payment and vouchers | 5/minute | 20/hour |
Invalid credentials are limited to 20 attempts per minute per source IP. A throttled request returns 429 and a Retry-After header.
Errors
| Status | Meaning |
|---|---|
400 | Invalid request, disallowed redirect origin, or invalid state |
401 | Missing, invalid, expired, or revoked key; inactive integration |
403 | Valid key without the required scope |
404 | Resource missing or owned by another integration |
409 | Stale application version or idempotency conflict |
429 | Rate limit exceeded; use Retry-After |
Every successful action, denied scoped action, throttling decision, inactive-key attempt, and admin key lifecycle change is durably audited without storing raw credentials.
Agent Keys
Agent Keys let an e-resident delegate specific API capabilities to an AI agent, CLI workflow, or third-party service without sharing their personal standard API key.
Agent recipes
End-to-end Agent Key workflows you can copy, paste, and run. The CLI path is recommended when your runtime can execute shell commands; direct HTTP examples are included for custom clients and environments where the CLI is not available.