Personal API Keys
Use Wesley from Claude, GPT, or scripts with your own permissions. Start with identity and client discovery, then call the same APIs as the app.
Personal API keys let tools use Wesley as the person who created the key, within that person's current paid-firm access. Trial and inactive firms cannot create or use personal API access. A user who belongs to both paid and trial firms can use a key only for the paid firms. Read-only access stays read-only. Removing a membership removes that access on subsequent requests. Expired or revoked keys and deleted, banned, or locked accounts cannot authenticate.
Connecting Claude or ChatGPT? Start with the MCP setup guide. Copy a setup prompt and approve the connection with your Wesley key.
For agents: start here
Base URL: https://www.wesley-ai.co
- Read this guide. It is public and needs no credential.
- Read
WESLEY_API_KEYfrom your runtime's secret configuration. Send it only in theAuthorizationheader to the Wesley origin. - Call
GET /api/users/meto verify whose key you have. - Call
GET /api/users/accounting-firms, select the intended firm, and callGET /api/users/companies?accountingFirmId=...to select the intended client. - Read the relevant API contract before making a request. Use returned IDs; ask the user when firm or client selection is ambiguous.
- Execute the requested workflow using the same
/api/...endpoints as the web app.
Machine-readable resources:
- This guide as Markdown
- API specification
- One endpoint's contract
- Transaction API contracts
- Documentation index
- All product guides as text
There is no separate /api/v1 base. In the specification, x-wesley-personal-api-key: true identifies handlers that accept personal keys. Other handlers can require an interactive session, an integration credential, or an internal service token. Key management itself requires signing in to Wesley. OAuth connection and reauthorization flows may require the user to finish in a browser.
Personal keys can call existing APIs marked x-wesley-personal-api-key: true; each endpoint still enforces its normal user, company, firm, role, quota, and approval checks. Database schema inspection and arbitrary SQL through /api/agent/read-query are never available to personal keys. Browser-session, service-credential, or other unmarked handlers remain unavailable. The public API specification describes HTTP contracts and remains available without a key.
Create a key
Sign in to Wesley, open API Keys & MCP from a firm's sidebar, select Create key, and create a personal key with a descriptive name, such as “Claude on my laptop.” Keys belong to you, including when this page is opened inside a firm. Other firm members cannot list or revoke your keys.
Copy the full key when it is first displayed; it cannot be retrieved later. Configure it as WESLEY_API_KEY in your agent's environment or secret store. The page's Copy agent instructions button provides the guide URL and startup instructions without including the secret.
Creating a key requires recent identity verification in Wesley. You can hold at most 10 active keys and create at most 10 keys per hour. Choose 7, 30, or 90 days (the default), or No expiration. A key without expiration remains usable until revoked, as long as your account and paid access remain eligible. It still counts toward the 10-key limit. The session-only creation endpoint accepts expiresInDays: null for no expiration; omitting the field keeps the 90-day default. Existing keys retain their expiration dates. Key creation sends an in-app security notification.
Revoke an unused or lost key from the same page. Revoke all keys provides immediate recovery if several tools may be compromised. Revocation remains available during rate-limit exhaustion or Redis outages. Create a replacement before revoking an old key if an automation needs an uninterrupted transition. The page shows the latest successful use and expiration. Revoke-all appears when you have multiple unrevoked keys; revoked keys are collapsed under their history.
Authenticate and identify the owner
These examples assume WESLEY_API_KEY has already been supplied securely. Do not place its literal value in shared commands, messages, URLs, or logs. Use an HTTP-capable agent or tool; fetching this public guide alone does not grant access to your data.
curl --fail-with-body 'https://www.wesley-ai.co/api/users/me' \
-H "Authorization: Bearer $WESLEY_API_KEY"Example response:
{
"user": {
"id": "user_example",
"name": "Example User",
"email": "user@example.com"
},
"apiKeyId": "00000000-0000-4000-8000-000000000001"
}The server determines the actor from the key. Do not submit a userId to impersonate another person. An explicitly supplied personal key determines the actor even if a browser session is also present. A bad or revoked personal key returns an error rather than falling back to a session.
Identify your tool and check usage
Send the optional X-Wesley-Client header on API requests. Use claude, chatgpt, codex, or script; other values become other, and a missing header becomes unspecified. This label is self-reported, grants no permissions, and does not prove which tool made the request. Do not put a person, company, prompt, or credential in this header.
curl --fail-with-body 'https://www.wesley-ai.co/api/users/me' \
-H "Authorization: Bearer $WESLEY_API_KEY" \
-H 'X-Wesley-Client: claude'The first completed HTTP response with a 2xx status marks first use; GET /api/users/me is a suitable connection check. A 202 response only means a job was accepted; check that job's status endpoint for completion. Keys do not maintain an online/offline connection session.
API Keys & MCP shows the latest successful request as Last used. First-success timestamps and detailed usage counts remain available to Wesley operators. Older authenticated-use timestamps are not relabeled as successful requests. Wesley operators can see hourly counts for up to 90 days by owner, key, normalized API route, HTTP method/status, coarse feature, and self-reported tool. They receive creation, first-use, revocation, known-key policy-block, and repeated-error events through Notiham in #noti-event.
This tracking does not store the key secret, request body, query strings, dynamic resource IDs, raw URL, IP address, or full user agent. Requests rejected before key identification are not attributed to an owner. Response tracking is best effort if the database/runtime is unavailable; key lifecycle events and queued notifications are durable.
Select a firm and client
curl --fail-with-body 'https://www.wesley-ai.co/api/users/accounting-firms' \
-H "Authorization: Bearer $WESLEY_API_KEY"Read the accountingFirms array and select the intended firm's id. Use that returned ID as FIRM_ID:
curl --fail-with-body --get 'https://www.wesley-ai.co/api/users/companies' \
-H "Authorization: Bearer $WESLEY_API_KEY" \
--data-urlencode "accountingFirmId=$FIRM_ID"Read the companies array and use the selected company's id as COMPANY_ID. The company list defaults to active clients; status=active,inactive includes both. Always provide the firm explicitly: without it, this endpoint selects one firm automatically and does not enumerate clients across all firms.
For client-specific handlers using company context, send x-company-id: $COMPANY_ID. For firm-specific handlers, send x-accounting-firm-id: $FIRM_ID. Follow the individual endpoint's contract when it requires IDs in the query, body, or path. These IDs select the target; they do not grant access to it.
Find the right operation
Fetch a small part of the specification rather than reading every API at once:
curl --fail-with-body --get 'https://www.wesley-ai.co/api/swagger.json' \
--data-urlencode 'path=/api/transactions'path matches an exact path template, including placeholders such as /api/document-conversion-files/{id}/download. tag matches a category such as Transactions, Reports, or Journal Entries. The full specification lists all paths and tags. Contracts are method-specific: explicit schemas identify required and optional fields, while fields inferred from handlers are optional unless stated otherwise. Read operation descriptions and validation errors for conditional rules. Do not invent endpoints, fields, or supported HTTP methods.
| Task | Existing endpoint | Notes |
|---|---|---|
| Confirm key owner | GET /api/users/me | Returns user and key ID |
| List firms | GET /api/users/accounting-firms | Returns accountingFirms |
| List clients | GET /api/users/companies | Query accountingFirmId; returns companies |
| List ledger accounts | GET /api/chart-of-accounts | Company context required |
| Read transactions | GET /api/transactions | Company context; use date filters and pagination |
| Read transaction summaries | GET /api/transactions/summary | Prefer summaries for counts and totals |
| Read journal entries | GET /api/journal-entries | Company context; inspect filters in the contract |
| Read P&L or balance sheet | GET /api/reports/statement | report_type, start_date, end_date, basis |
| Import bank statements | POST /api/bank-statement-imports | Multipart upload; current write permission required |
| Read conversion progress | GET /api/document-conversion-files | Query status=all to include completed/reviewable files |
Example: read transactions
curl --fail-with-body --get 'https://www.wesley-ai.co/api/transactions' \
-H "Authorization: Bearer $WESLEY_API_KEY" \
-H "x-company-id: $COMPANY_ID" \
--data-urlencode 'start_date=2026-08-01' \
--data-urlencode 'end_date=2026-08-31' \
--data-urlencode 'page=1' \
--data-urlencode 'page_size=25'Use the returned pagination metadata. Read additional pages only when needed. For whole-period totals, use summary/report endpoints rather than summing one page of transactions.
Example: read a profit and loss statement
curl --fail-with-body --get 'https://www.wesley-ai.co/api/reports/statement' \
-H "Authorization: Bearer $WESLEY_API_KEY" \
-H "x-company-id: $COMPANY_ID" \
--data-urlencode 'report_type=profit-loss' \
--data-urlencode 'start_date=2026-08-01' \
--data-urlencode 'end_date=2026-08-31' \
--data-urlencode 'basis=accrual'Use report_type=balance-sheet for a balance sheet. Select cash or accrual according to the user's request.
Example: upload a bank statement
Choose the appropriate banking account from the client's accounts and set ACCOUNT_ID to its returned ID. Set IMPORT_ID to a new UUID for this import, and preserve that UUID if retrying the same files and parameters.
curl --fail-with-body 'https://www.wesley-ai.co/api/bank-statement-imports' \
-H "Authorization: Bearer $WESLEY_API_KEY" \
-H "x-company-id: $COMPANY_ID" \
-F "companyId=$COMPANY_ID" \
-F "accountId=$ACCOUNT_ID" \
-F "idempotencyKey=$IMPORT_ID" \
-F 'statements=@/absolute/path/to/statement.pdf;type=application/pdf'Let your HTTP client set the multipart boundary. A successful response includes batchId and files; acceptance is not completion. Follow conversion progress using /api/document-conversion-files?status=all with the same company header. Review that endpoint's response and the uploaded file identifiers rather than assuming every returned document belongs to this import. Poll at modest intervals, such as 5–10 seconds with backoff.
Imports use the client's normal Wesley processing and automation settings and may affect bookkeeping data. Run them only as part of the user's requested workflow. Spreadsheet imports can require sheet selection and confirmed mappings; inspect the contract and Supported Files.
Errors and retries
| Response | What to do |
|---|---|
400 | Read the validation error and fix the request. Check the method-specific contract. |
401 | Check the credential configuration. Ask the owner to replace a revoked key or resolve an inactive account. |
403 | The current user lacks permission, or the operation requires an interactive session. Do not substitute another client's ID. |
404 | Check IDs and the documented path. Some handlers conceal inaccessible resources as not found. |
405 | Use a method listed for that endpoint. |
409 | Read the error code. Reuse operation IDs only with identical content. For unknown email delivery, check sent mail before starting another operation. |
410 | The legacy route was retired; follow the replacement named in the response. |
429 | Respect Retry-After when present and retry with backoff. |
503 or other 5xx | Back off on reads. Check whether a write completed before retrying it. |
Response envelopes vary between existing endpoints; inspect the documented response instead of assuming a global data wrapper. A redirect or HTML sign-in page usually means the URL is a browser route. Use the documented /api/... handler.
Wesley does not provide a universal Idempotency-Key guarantee across all existing APIs. Bank-statement imports use the idempotencyKey field; AP/AR payment creation uses operationId.
POST /api/client-emails/send, /api/accounting-firms/gmail/send, /api/accounting-firms/outlook/send, and /api/accounting-firms/clients-bulk-email require a UUID Idempotency-Key header (or operationId body field). Preserve it for identical retries. Completed sends return their saved outcome. A changed payload returns IDEMPOTENCY_CONFLICT; a running or uncertain send is not automatically repeated. Query GET /api/users/operations?operationId=... for your operation status. Bulk email tracks each client's delivery separately, so retrying the same batch does not repeat already completed sends. Check provider sent mail before creating a new ID for an uncertain delivery.
Usage and job limits
Limits aggregate across all of your keys. Creating extra keys does not increase your user allowance.
| Limit | Allowance |
|---|---|
| Requests per key | 300 per minute |
| Requests per user | 600 per minute |
| Writes per user | 180 per minute |
| Heavy requests per user | 20 per minute; 1,000 per day |
| Heavy requests per firm | 5,000 per day |
| Concurrent heavy requests | 3 per user; 12 per firm |
| Personal document conversion | 2,000 pages per day; at most 500 selected pages per request |
| Connected email delivery | 500 recipients per user per day; 2,500 per firm per day |
IP and upload quotas also apply. Limits use distributed server-side counters and may return 429 with Retry-After. If the safety service cannot verify capacity, work fails with 503; do not increase concurrency to work around it. Daily quotas use a 24-hour window starting with the first request, rather than resetting at midnight.
Document conversion reserves credits before starting paid work, then settles the reservation. Files must be uploaded to Wesley under your own document upload session or be existing documents you can access. Arbitrary external URLs and another user's private file references are rejected. POST /api/document-upload-session requires authentication and returns your private upload prefix and upload-token endpoint. Anonymous document uploads are not supported. Upload documents through Banking to start conversion.
Approve destructive actions
Company deletion, full reset, date-range deletion, and firm deletion require a one-time approval. The API key alone cannot approve them. Ordinary reads and edits do not require an interactive challenge.
- Prepare
POST /api/users/action-approvalswithaction, the targetcompanyId(oraccountingFirmIdfor firm deletion), andpayload. - Keep the returned
tokenin memory or a secret store. Give the user the returnedapprovalUrl, resolved against the Wesley base URL. Never put the token in the URL. - The user opens Wesley, reviews the exact target and action, enters its name, and completes identity verification when requested.
- Poll
GET /api/users/action-approvals?id=...at modest intervals untilapproved_atis present. - Send the original action with
x-wesley-action-approval: <token>. Use the same personal key, user, company, and payload. Approval expires after five minutes and is consumed by one matching execution attempt. An expired or consumed approval requires a new user approval.
| Action | Approval payload | Execution |
|---|---|---|
company.reset | { "disconnectPlaidConnections": false } | POST /api/companies/reset |
company.delete | {} | DELETE /api/companies/delete |
company.delete-range | { "startDate": "2026-01-01", "endDate": "2026-01-31" } | POST /api/companies/delete-range |
firm.delete | {} | DELETE /api/accounting-firms/delete |
Provide the normal company or firm context on execution. Server permissions are checked again at execution time. After any timeout, inspect the resulting state before requesting another approval.