# e-Próspera API — Agent Reference This file is a plain-text, single-fetch summary of the public e-Próspera API, intended for ingestion by AI agents. Maintained by hand alongside the rendered docs at https://portal.eprospera.com/api-docs/. ============================== BASE URL ============================== Production : https://portal.eprospera.com Staging : https://staging-portal.eprospera.com All endpoint paths in this document are relative to one of these origins. The API ships from the same host as the consumer portal — there is no separate api.eprospera.com. ============================== AUTHENTICATION ============================== Header on every authenticated request: Authorization: Bearer Token types: sk-... Standard API key. Personal use, unscoped, never share. ak-... Agent Key. Scope-checked. Owner-delegated. OAuth 2.0 access token (RS256), issued by /api/oauth/token. Endpoint compatibility: - /api/v1/verify_rpn, /api/v1/registries/..., /api/v1/legal_entities/..., /api/v1/legal_entity_applications/..., /api/v1/referral-codes/... Accept sk- and ak- (with required scope). Agent-key reads of legal-entity records are restricted to entities created via the API (createdViaAPI=true). - /api/v1/me/natural-person* Accepts OAuth tokens AND Agent Keys, NOT sk-. - /api/v1/me/legal-entities* OAuth ONLY. ============================== RESPONSE ENVELOPES ============================== Single resource { "data": { ... } } Collection { "data": [ ... ] } (see Pagination) Create { "data": { ... }, "nextSteps": { ... } } Coupon-pay { "success": true, "data": { ... }, "message": "..." } Search { "results": [ ... ] } verify_rpn { "result": "...", "active": true|false } OAuth/JWKS endpoints follow RFC 6749 / 7517 / OIDC Core 1.0 directly and do not use a `data` wrapper. ============================== ERROR ENVELOPE ============================== Non-2xx body shape: { "error": "code", "error_description": "...", "details": [ ... ] } `error_description` and `details` are optional. Some legacy endpoints return only `{ "error": "..." }`. `error` is always present on a non-2xx response. `details` on 400 responses is typically a Zod issue array: { "code", "expected", "received", "path": [...], "message" } ============================== HTTP STATUS CODES ============================== 200 Success. 400 Validation error or conflicting state. 401 Missing/malformed/invalid credential. Re-authenticate. 403 Credential valid but lacks the required scope. 404 Resource does not exist OR caller cannot see it. Indistinguishable. 409 Conflict (e.g. legal-entity name already taken). 429 Rate limit exceeded. 500 Server error. Retry-with-backoff only for idempotent operations. ============================== TIMESTAMPS, IDS, NULLABILITY ============================== Timestamps ISO 8601 / RFC 3339 UTC strings, e.g. "2024-01-15T10:30:00.000Z". IDs RFC 4122 v4 UUIDs (lowercase, dashed) unless noted. RPN 14-digit numeric string. Starts with `8` (legal entity) or `9` (natural person). Nullability Optional fields explicitly typed `T | null` and present as `null`. ============================== RATE LIMITS ============================== POST /api/v1/verify_rpn 5,000 / 24h, 50 / min, per API key. POST /api/v1/registries/legal_entities/search 5,000 / 24h, 50 / min, per API key. All other endpoints No documented limit today. 429 response: { "error": "Rate limit exceeded", ... }. No Retry-After header today; back off exponentially. ============================== PAGINATION ============================== List endpoints other than /api/v1/nomadlayer/applications are NOT currently paginated. They return the full result set under a top-level `data` array. Design clients to tolerate a future `pagination` envelope being added without breaking. The pattern we will converge on (already shipped on /api/v1/nomadlayer/applications): { "data": [ ... ], "pagination": { "page": 1, "pageSize": 20, "totalCount": 134, "pageCount": 7 } } Future query params: ?page= (default 1, min 1), ?pageSize= (default 20, max 100). Until then, no pagination params are accepted on these endpoints. ============================== VERSIONING ============================== Stable surface: /api/v1/. Breaking changes go to a new /api/v2/. Additive changes (new fields, new optional query params, new endpoints) can land under /api/v1/ without notice. Clients must ignore unknown response fields. ============================== IDEMPOTENCY & WEBHOOKS ============================== No Idempotency-Key header today. POST /legal_entity_applications is not idempotent — retrying creates duplicate Drafts. Deduplicate caller-side. No public webhooks. Discover state changes by polling. Recommended cadence: 30s for the first 2 minutes, then back off to 5 minutes. Approved/Rejected are terminal — stop polling. ============================== AGENT KEY SCOPES ============================== Read scopes (no Manifestation of Will required): agent:person.details.read Personal details: legal name, RPN, DOB, address, phone. agent:person.residency.read Current residency status and type. agent:person.id_verification.read ID verification document/selfie artifacts. agent:entity.read Read API-created legal entity records. agent:entity.documents.read Read documents for API-created legal entities. agent:entity.application.read Read your API-created legal-entity applications. agent:verify_rpn Verify whether an RPN exists and is active. agent:registry.search Search the legal-entity registry. Write scopes (REQUIRE active Manifestation of Will signed by the key owner): agent:entity.application.create Create legal-entity applications. agent:entity.application.pay Apply coupon payments to applications. Revoking the Manifestation of Will auto-revokes all linked write-capable Agent Keys. ============================== OAUTH SCOPES ============================== Standard OIDC: openid Authentication; requires `nonce` parameter on /authorize. profile name, given_name, family_name, picture. email email, email_verified. offline_access Refresh token is issued. Próspera-specific (claim sets returned by /api/v1/me/* endpoints): eprospera:person.details.read eprospera:person.residency.read eprospera:person.id_verification.read eprospera:entity.read eprospera:entity.documents.read ============================== ENDPOINTS — REST API ============================== POST /api/v1/verify_rpn Auth : sk- or ak- with agent:verify_rpn Body : { "rpn": "<14-digit string starting 8 or 9>" } Response : { "result": "found_legal_entity"|"found_natural_person"|"not_found", "active": boolean } Errors : 400 (invalid rpn), 401, 403, 429, 500 Limits : 5,000/day and 50/min per key. POST /api/v1/registries/legal_entities/search Auth : sk- or ak- with agent:registry.search Body : { "query": "" } Response : { "results": [ { id, name, extension, residentPermitNumber } ] } Behavior : Case-insensitive. Multi-word queries split on whitespace. RPN partial match. Errors : 400, 401, 403, 429, 500 GET /api/v1/legal_entities/{id} Auth : sk- or ak- with agent:entity.read Path : id = UUID Response : { "data": { id, optionId, type, name, extension, nameStartsWithExtension, formationDate, registrationDate, dissolutionDate, createdAt, principalOfficeAddress: { line1, line2, city, state, postalCode, country }, residentPermitNumber } } Notes : Agent Keys can only access entities where residencyApplication.createdViaAPI=true. Errors : 401, 403, 404, 500 GET /api/v1/legal_entities/{id}/documents Auth : sk- or ak- with agent:entity.documents.read Response : { "data": [ { id, name, createdAt, slug, version, fileUrl } ] } Pagination: NOT paginated; full result set returned. Errors : 401, 403, 404, 500 GET /api/v1/legal_entity_applications Auth : sk- or ak- with agent:entity.application.read Response : { "data": [ { id, statusId, applicationData, applicationVersion, submittedAt, createdAt, approvedAt, rejectedAt, legalEntityId } ] } Status : "Draft" | "Pending Review" | "Approved" | "Rejected". Notes : Agent Keys see only API-created applications (createdViaAPI=true). `applicationData` is the stored internal format; not the same shape as the create body. Pagination: NOT paginated. Errors : 401, 500 POST /api/v1/legal_entity_applications Auth : sk- or ak- with agent:entity.application.create Body : { "applicationData": { "residencyType": "e-Resident" | "Resident", "entityType": "llc", "name": "...", "extension": "", "principalOffice": { country, line1, line2, city, state, postalCode }, "contactEmail": "...", "registeredAgentProvider": "prospera_employment_solutions" | null, "registeredAgentDetails": null | { attn, residentPermitNumber, officeAddress, mailingAddress }, "analytics": { industry?, whatIsYourBusinessIntendingToDo?, howDidYouHearAboutProspera?, whyChooseProspera?, website? } }, "referralCode": "..." (optional), "redirectUrl": "..." (optional) } Response : { "data": { application object }, "nextSteps": { "signature": "" | null } } nextSteps.signature null → AOC pre-accepted; application auto-progresses on payment. nextSteps.signature URL → human browser session required to sign. Prereqs : Owner is active e-Resident or Resident. Write Agent Keys require active MoW. Errors : 400 (schema, name conflict, agent config), 401, 403, 409, 500 GET /api/v1/legal_entity_applications/{id} Auth : sk- or ak- with agent:entity.application.read Response : { "data": { same shape as list element } } Errors : 401, 404, 500 POST /api/v1/legal_entity_applications/{id}/pay/coupon Auth : sk- or ak- with agent:entity.application.pay Body : { "couponCode": "..." } Response : { "success": true, "data": { application object }, "message": "..." (optional) } Behavior : If application is signed (or AOC pre-accepted), submits the application after payment. If unsigned, returns success with a message indicating signature is still required. Coupon must fully cover the invoice; partial coupons are rejected. Errors : 400 (not Draft, not API-created), 401, 403, 404, 500 Side eff : Audited; emails the account owner on success. POST /api/v1/legal_entity_applications/{id}/checkout_session Auth : sk- only. Agent Key checkout sessions are temporarily disabled. Body : { "paymentProvider": "stripe"|"stripe-crypto"|"lnbits"|"solana-pay-ptc", "redirectUrl": "", "email": "..." | null } Response : { "data": { "url": "" }, "invoiceId": "..." } Behavior : Reuses any unpaid invoice on the application; otherwise creates one. Errors : 400, 401, 403, 404, 503 for Agent Key calls, 500 Side eff : Standard-key success creates a checkout session. Agent Key calls are audited and rejected. GET /api/v1/referral-codes/{code}/referrals Auth : sk- only (Agent Keys NOT supported on this route). Response : { "code": "...", "naturalPersons": [ { fullName: string|null, referredAt: string } ], "legalEntities": [ { name, rpn, referredAt: string|null } ] } Notes : Natural-person referrals can appear before approval (fullName may be null). Eligible referees auto-receive 20% off first paid residency payment (not in payload). Pagination: NOT paginated. Errors : 401, 404, 500 ============================== ENDPOINTS — /api/v1/me/* ============================== GET /api/v1/me/natural-person Auth : OAuth (eprospera:person.details.read) OR Agent Key (agent:person.details.read). NOT sk-. Response : object | null. Object fields: givenName, surname, name, residentPermitNumber, countryOfBirth, citizenships[], dateOfBirth, sex ("M"|"F"|null), address { country,line1,line2,city,state,postalCode } | null, phoneNumber. Returns null when the owner has no approved residency. Errors : 401, 403, 500 GET /api/v1/me/natural-person/id-verification (alias /id_verification) Auth : OAuth (eprospera:person.id_verification.read) OR Agent Key (agent:person.id_verification.read). Response : { id, type, date, status: "approved"|null, documents: { documentFront, documentBack, face } } All fields may be null if no completed verification. Document URLs are signed and expire ~1 hour — download promptly. Errors : 401, 403, 500 GET /api/v1/me/natural-person/residency Auth : OAuth (eprospera:person.residency.read) OR Agent Key (agent:person.residency.read). Response : { wasEverResident: boolean, activeResidency: null | { effectiveDate, terminationDate, residencyType, version } } residencyType ∈ "Limited e-Resident" | "e-Resident" | "Resident". Errors : 401, 403, 500 GET /api/v1/me/legal-entities Auth : OAuth ONLY (eprospera:entity.read). Agent Keys NOT supported. Response : { "data": [ { id, optionId, type, name, extension, nameStartsWithExtension, residentPermitNumber } ] } Pagination: NOT paginated. Errors : 401, 500 GET /api/v1/me/legal-entities/{id} Auth : OAuth ONLY (eprospera:entity.read). Response : { "data": { id, optionId, type, name, extension, nameStartsWithExtension, formationDate, principalOfficeAddress, registrationDate, dissolutionDate, residentPermitNumber } } Errors : 401, 404, 500 GET /api/v1/me/legal-entities/{id}/documents Auth : OAuth ONLY (eprospera:entity.documents.read). Response : { "data": [ { id, name, slug, version, fileUrl } ] } Pagination: NOT paginated. Errors : 401, 404, 500 ============================== ENDPOINTS — OAUTH 2.0 / OIDC ============================== GET /.well-known/openid-configuration Discovery document. issuer = https://portal.eprospera.com. Cache-Control: public, max-age=3600. GET /api/oauth/.well-known/jwks.json RS256 public keys. Cache-Control: public, max-age=3600. GET /api/oauth/authorize Required query: client_id, redirect_uri, response_type=code, scope (space-delimited), state. Required if scope includes openid: nonce. Optional: response_mode (query|fragment|form_post; default query), code_challenge + code_challenge_method=S256 (PKCE). Behavior: Redirects user to login + consent. Returns to redirect_uri with code+state on approval. On denial, redirects with error=access_denied&state=... POST /api/oauth/token Content-Type: application/x-www-form-urlencoded. Client auth: client_secret_basic OR client_secret_post. Grant types: grant_type=authorization_code Form: code, redirect_uri, code_verifier (if PKCE). grant_type=refresh_token Form: refresh_token, scope (optional, must be subset of original). Response (always): access_token, token_type=Bearer, expires_in=3600, scope. Conditional: id_token (if openid granted), refresh_token (if offline_access granted). Refresh tokens rotate on every use. Headers: Cache-Control: no-store, Pragma: no-cache. Errors: invalid_request, invalid_grant, invalid_scope, unsupported_grant_type, invalid_client, internal_server_error. GET /api/oauth/userinfo Auth: Bearer access token. Always: { sub }. If `profile` scope: name, given_name, family_name, picture. If `email` scope: email, email_verified. Errors: 401 (missing_token, invalid_token), 404 (user_not_found). For Próspera-specific data, use /api/v1/me/* instead. ============================== AGENT WORKFLOW: AUTOMATED LLC INCORPORATION ============================== Required scopes: agent:entity.application.create agent:entity.application.pay agent:entity.application.read agent:entity.read Prereqs: 1. Owner is active e-Resident or Resident. 2. Owner has signed Manifestation of Will. 3. Owner pre-accepted the Agreement of Coexistence (AOC) for the relevant residency type during MoW signing — without this, step 1 returns nextSteps.signature URL requiring browser. Steps: 1. POST /api/v1/legal_entity_applications → returns app id and nextSteps.signature. 2. Coupon path: POST /api/v1/legal_entity_applications/{id}/pay/coupon { couponCode }. Hosted checkout is currently standard-key only; Agent Keys should use coupons or resident portal payment. 3. Poll GET /api/v1/legal_entity_applications/{id} every 30s → 5 min until statusId is "Approved" (data.legalEntityId populated) or "Rejected" (terminal). 4. GET /api/v1/legal_entities/{legalEntityId} for the resulting record. ============================== COMMON ERROR CAUSES ============================== 400 Body fails Zod validation (`details` lists offending paths) OR state precondition violated ("application is not in Draft status", coupon under-funds invoice, etc.). 401 Missing/invalid Authorization, key revoked, OR JWT expired. 403 Agent Key missing required scope, OR MoW revoked (auto-revokes write-capable Agent Keys). 404 Resource does not exist OR is invisible to your credential. For Agent Keys on /legal_entities/* and /legal_entity_applications/*, this often means the resource was created outside the API (createdViaAPI=false) and is therefore not visible. 409 Entity name conflict on POST /legal_entity_applications. 429 Rate limit exceeded (verify_rpn / registry search). 500 Server error. Retry only on read endpoints with exponential backoff. Coupon validation failures sometimes surface as 500; the owner is notified by email automatically.