SKILL.md
Docyrus API Developer
Integrate with the Docyrus API using @docyrus/api-client (REST client) and @docyrus/signin (React auth provider). Authenticate via OAuth2 PKCE, query data sources with powerful filtering/aggregation, and consume REST endpoints.
Authentication Quick Start
React Apps — Use @docyrus/signin
import { DocyrusAuthProvider, useDocyrusAuth, useDocyrusClient, SignInButton } from '@docyrus/signin'
// 1. Wrap root
<DocyrusAuthProvider
apiUrl={import.meta.env.VITE_API_BASE_URL}
clientId={import.meta.env.VITE_OAUTH2_CLIENT_ID}
redirectUri={import.meta.env.VITE_OAUTH2_REDIRECT_URI}
scopes={['offline_access', 'Read.All', 'DS.ReadWrite.All', 'Users.Read']}
callbackPath="/auth/callback"
// 2. Use hooks
function App() {
const { status, signOut } = useDocyrusAuth()
const client = useDocyrusClient() // RestApiClient | null
if (status === 'loading') return
if (status === 'unauthenticated') return
// client is ready — make API calls
const user = await client!.get('/v1/users/me')
}
### Non-React / Server — Use OAuth2Client Directly
import { RestApiClient, OAuth2Client, OAuth2TokenManagerAdapter, BrowserOAuth2TokenStorage } from '@docyrus/api-client'
const tokenStorage = new BrowserOAuth2TokenStorage(localStorage)
const oauth2 = new OAuth2Client({
baseURL: 'https://api.docyrus.com',
clientId: 'your-client-id',
redirectUri: 'http://localhost:3000/callback',
usePKCE: true,
tokenStorage,
})
// Auth Code flow
const { url } = await oauth2.getAuthorizationUrl({ scope: 'openid offline_access Users.Read' })
window.location.href = url
// After redirect:
const tokens = await oauth2.handleCallback(window.location.href)
// Create API client with auto-refresh
const client = new RestApiClient({
baseURL: 'https://api.docyrus.com',
tokenManager: new OAuth2TokenManagerAdapter(tokenStorage, async () => {
return (await oauth2.refreshAccessToken()).accessToken
}),
})
## API Endpoints
### Data Source Items (Dynamic per tenant)
GET /v1/apps/{appSlug}/data-sources/{slug}/items — List with query payload
GET /v1/apps/{appSlug}/data-sources/{slug}/items/{id} — Get one
POST /v1/apps/{appSlug}/data-sources/{slug}/items — Create
PATCH /v1/apps/{appSlug}/data-sources/{slug}/items/{id} — Update
DELETE /v1/apps/{appSlug}/data-sources/{slug}/items/{id} — Delete one
DELETE /v1/apps/{appSlug}/data-sources/{slug}/items — Delete many (body: { recordIds })
Endpoints exist only if the data source is defined in the tenant. Check the tenant's OpenAPI spec at `GET /v1/api/openapi.json`.
### System Endpoints (Always Available)
GET /v1/users — List users
POST /v1/users — Create user
GET /v1/users/me — Current user profile
PATCH /v1/users/me — Update current user
### Connector Discovery & External Request Endpoints
GET /v1/connectors?q=&limit=&offset= — List connectors with keyword search
GET /v1/connectors/{dataProviderSlug} — Get connector detail (dataSources + actions)
GET /v1/connectors/{dataProviderSlug}/actions/{actionKey} — Get action detail (input/output schemas, API endpoint)
GET /v1/connectors/{dataProviderSlug}/connections — Get tenant connections + user connection status
PUT /v1/connectors/{dataProviderSlug} — Send HTTP request through connector provider auth
Scopes: `Read.All`, `ReadWrite.All`, or `Connectors.Read.All`. The `PUT` endpoint requires `ReadWrite.All`.
**PUT request body** for sending requests through a connector:
{
"endpoint": "relative/path/or/absolute-url",
"requestMethod": "GET",
"data": { "fields": "id,name", "limit": 20 },
"contentType": "application/json",
"headers": { "Authorization": "Bearer <override-token>" },
"connectionId": "optional-tenant-connection-uuid",
"connectionAccountId": "optional-connection-account-uuid"
}
The connector resolves auth credentials (OAuth tokens, base URL) from the provider configuration and stored connections. Custom `headers.Authorization` overrides the stored token.
### Action Run Endpoints
GET /v1/apps/base/actions — List base actions
GET /v1/apps/{appSlug}/actions/{actionSlug} — Get action metadata
POST /v1/apps/{appSlug}/actions/{actionSlug}/run — Run action directly
Action run accepts arbitrary JSON body as input. Optional headers: `x-connection-id`, `x-connection-account-id`.
### Studio (Dev) Schema Endpoints
The studio surface manages dev-app schema objects. Most routes are gated by the `Architect.Read.All` / `Architect.ReadWrite.All` scopes, and the app is identified by its tenant `app_id` UUID.
Apps (mutations only — list uses /v1/apps)
DELETE /v1/dev/apps/{appId} — Archive app
POST /v1/dev/apps/{appId}/restore — Restore archived app
DELETE /v1/dev/apps/{appId}/permanent — Permanently delete app
Data sources
GET /v1/dev/apps/{appId}/data-sources — List data sources (?expand=fields,...)
GET /v1/dev/apps/{appId}/data-sources/{dataSourceId} — Get data source
POST /v1/dev/apps/{appId}/data-sources — Create data source
PATCH /v1/dev/apps/{appId}/data-sources/{dataSourceId} — Update data source
DELETE /v1/dev/apps/{appId}/data-sources/{dataSourceId} — Archive data source
POST /v1/dev/apps/{appId}/data-sources/{dataSourceId}/restore — Restore archived data source
DELETE /v1/dev/apps/{appId}/data-sources/{dataSourceId}/permanent — Permanently delete data source
POST /v1/dev/apps/{appId}/data-sources/bulk — Bulk create (body: { dataSources })
Fields
GET /v1/dev/apps/{appId}/data-sources/{dataSourceId}/fields — List fields
GET /v1/dev/apps/{appId}/data-sources/{dataSourceId}/fields/{fieldId} — Get field
POST /v1/dev/apps/{appId}/data-sources/{dataSourceId}/fields — Create field
PATCH /v1/dev/apps/{appId}/data-sources/{dataSourceId}/fields/{fieldId} — Update field
DELETE /v1/dev/apps/{appId}/data-sources/{dataSourceId}/fields/{fieldId} — Delete field
POST /v1/dev/apps/{appId}/data-sources/{dataSourceId}/fields/batch — Bulk create (body: { fields })
PATCH /v1/dev/apps/{appId}/data-sources/{dataSourceId}/fields/batch — Bulk update (body: { fields[].fieldId })
DELETE /v1/dev/apps/{appId}/data-sources/{dataSourceId}/fields/batch — Bulk delete (body: { fieldIds })
Field enums
GET /v1/dev/apps/{appId}/data-sources/{dataSourceId}/fields/{fieldId}/enums — List enum options
POST /v1/dev/apps/{appId}/data-sources/{dataSourceId}/fields/{fieldId}/enums — Create enums (body: { enums })
PATCH /v1/dev/apps/{appId}/data-sources/{dataSourceId}/fields/{fieldId}/enums — Update enums (body: { enums[].enumId })
DELETE /v1/dev/apps/{appId}/data-sources/{dataSourceId}/fields/{fieldId}/enums — Delete enums (body: { enumIds })
Data views (saved views) — slug-scoped
GET /v1/apps/{appSlug}/data-sources/{dataSourceSlug}/views — List views
GET /v1/apps/{appSlug}/data-sources/{dataSourceSlug}/views/{viewId} — Get view
POST /v1/apps/{appSlug}/data-sources/{dataSourceSlug}/views — Create view
PUT /v1/apps/{appSlug}/data-sources/{dataSourceSlug}/views/{viewId} — Update view
DELETE /v1/apps/{appSlug}/data-sources/{dataSourceSlug}/views/{viewId} — Delete view
Forms (record-entry layouts) — slug-scoped
GET /v1/apps/{appSlug}/data-sources/{dataSourceSlug}/forms — List forms
GET /v1/apps/{appSlug}/data-sources/{dataSourceSlug}/forms/{formId} — Get form
POST /v1/apps/{appSlug}/data-sources/{dataSourceSlug}/forms — Create form
PUT /v1/apps/{appSlug}/data-sources/{dataSourceSlug}/forms/{formId} — Update form
DELETE /v1/apps/{appSlug}/data-sources/{dataSourceSlug}/forms/{formId} — Delete form
Webforms (public-facing forms)
GET /v1/dev/webforms — List webforms (?dataSourceId)
GET /v1/dev/webforms/{webformId} — Get webform
POST /v1/dev/webforms — Create webform
PATCH /v1/dev/webforms/{webformId} — Update webform
DELETE /v1/dev/webforms/{webformId} — Delete webform
HTML / PDF / DOCX export templates
GET /v1/dev/html-templates — List (?dataSourceId,&isDefault,&limit,&offset)
GET /v1/dev/html-templates/{templateId} — Get template
POST /v1/dev/html-templates — Create template
PUT /v1/dev/html-templates/{templateId} — Update template
DELETE /v1/dev/html-templates/{templateId} — Delete template
Email templates
GET /v1/dev/email-templates — List (?dataSourceId,&limit,&offset)
GET /v1/dev/email-templates/{templateId} — Get template
POST /v1/dev/email-templates — Create template
PUT /v1/dev/email-templates/{templateId} — Update template
DELETE /v1/dev/email-templates/{templateId} — Delete template
Notes:
- The bulk update DTOs do not mirror the list/get response shapes. Send `fields[].fieldId` and `enums[].enumId` (not `id`).
- A webform created without `dataSourceId` posts submissions into the tenant-schema `webform_record` table instead of a data source.
- Archived data sources cannot be reliably resolved by slug; use the ID for `restore` and `permanent` routes.
### Automation Endpoints
Tenant-app automation CRUD plus typed trigger and action node mutations. Gated by `Architect.Read.All` / `Architect.ReadWrite.All`.
Automations
GET /v1/dev/apps/{appId}/automations — List automations
GET /v1/dev/apps/{appId}/automations/{id} — Get automation (includes triggers)
POST /v1/dev/apps/{appId}/automations — Create automation + first trigger
PATCH /v1/dev/apps/{appId}/automations/{id} — Update automation (name, status, source_data_source_id)
DELETE /v1/dev/apps/{appId}/automations/{id} — Delete automation (204)
Triggers — typed create/update, type-independent delete
POST /v1/dev/apps/{appId}/automations/{automationId}/triggers/{type} — Create trigger
PATCH /v1/dev/apps/{appId}/automations/{automationId}/triggers/{type}/{triggerId} — Update trigger
DELETE /v1/dev/apps/{appId}/automations/{automationId}/triggers/{triggerId} — Delete trigger (204)
Action nodes — typed create/update, type-independent delete
GET /v1/dev/apps/{appId}/automations/{automationId}/nodes — List nodes
GET /v1/dev/apps/{appId}/automations/{automationId}/nodes/{nodeId} — Get node
POST /v1/dev/apps/{appId}/automations/{automationId}/nodes/{type} — Create node
PATCH /v1/dev/apps/{appId}/automations/{automationId}/nodes/{type}/{nodeId} — Update node
DELETE /v1/dev/apps/{appId}/automations/{automationId}/nodes/{nodeId} — Delete node (204)
Trigger `{type}` values (kebab-case URL segments): `record-created`, `record-modified`, `record-deleted`, `recurrence`, `app-event`, `webhook`, `emailhook`, `webform`, `button-activation`, `manual-activation`.
Action node `{type}` values: `external-action`, `send-email`, `send-notification`, `create-record`, `update-records`, `request-approval`, `request-input`, `http-request`, `data-source-query`, `custom-query`, `generate-document`, `ai-prompt`, `ai-agent`, `execute-script`.
`POST /v1/dev/apps/{appId}/automations` accepts `trigger_type` in camelCase (e.g. `recordCreated`, `recordModified`, `recordDeleted`, `recurrence`, `appEvent`, `webhook`, `emailhook`, `webform`, `buttonActivation`, `manualActivation`) on `CreateAutomationDto`. The typed trigger CRUD endpoints use the kebab-case form in the URL.
Request bodies use `snake_case` keys (e.g. `source_data_source_id`, `max_run_per_record`, `modified_columns`, `recurrence_frequency`, `core_data_provider_id`, `webhook_id`, `tenant_webform_id`, `action_type_id`, `field_mapping`, `dynamic_field_mapping`, `condition`, `input_template`, `input_transformer`, `custom_headers`, `pre_action_request`, `post_action_request`, `target_data_source_condition`).
Important: creating a node with `type=external-action` requires `action_type_id` (maps to `core_action.id`). The backend validates the supplied `data` against `core_action.input_json_schema` and inserts the linked `tenant_action` row in the same transaction.
### Action / Approval RPC (Production)
These are separate from the dev-app automation CRUD above. They drive the runtime engine.
PUT /v1/automation/processAction — Execute action payload (IActionPayload)
POST /v1/automation/exchange-rates (alias /v1/automation/syncExchangeRates) — Fetch/save FX rates (admin or api)
PUT /v1/automation/sendApprovalRequests — { approvalStatusFieldId, recordId }
PUT /v1/automation/sendApprovalResponse — Approve response
PUT /v1/automation/sendApprovalRevisionRequest — Approval revision request
PUT /v1/automation/sendPushNotification/{notificationId} — Push notification (admin or api)
### Messaging Endpoints
Tenant email accounts and transactional send. All routes require the `Messaging.Email.Send` OAuth2 scope.
GET /v1/messaging/email/accounts — List active tenant email accounts (no credentials)
POST /v1/messaging/email/accounts/{accountId}/send — Send email through an account
`POST /v1/messaging/email/accounts/{accountId}/send` body (`SendEmailDto`):
{
"to": ["user@example.com"],
"cc": ["manager@example.com"],
"bcc": ["audit@example.com"],
"replyTo": ["support@example.com"],
"subject": "Daily summary",
"body": "<p>Hello</p>",
"sendAsUser": false,
"attachments": [
{ "filePath": "records/abc/attachments/foo.pdf", "fileName": "foo.pdf", "mimeType": "application/pdf" }
]
}
Limits: `to`/`cc`/`bcc`/`replyTo` accept up to 50 RFC-5322 addresses each, subject is capped at 998 characters, body at 1 000 000 characters, attachments at 10 items, and `filePath` at 2048 characters. `sendAsUser` only takes effect when the account allows it (see `allowOverrideName` / `allowOverrideEmail` from the accounts list).
`EmailAccountDto` (returned by list) exposes: `id`, `name`, `provider`, `senderEmail`, `senderName`, `isUserAccessible`, `allowOverrideName`, `allowOverrideEmail`, `createdOn`. Credentials, tokens, and provider secrets are never returned.
`SendEmailResponseDto`: `{ messageId, provider, accepted, rejected }`.
### ACL / Role Management Endpoints
GET /v1/users/acl?dataSourceId={uuid}&recordId={uuid} — Read record ACL rows
POST /v1/users/acl/share — Upsert record shares
DELETE /v1/users/acl/share — Revoke record shares
PUT /v1/users/acl/owner — Transfer record ownership
GET /v1/users/acl/roles — List roles
GET /v1/users/acl/roles/{roleId} — Get one role
POST /v1/users/acl/roles — Create role
PATCH /v1/users/acl/roles/{roleId} — Update role
DELETE /v1/users/acl/roles/{roleId} — Delete role
GET /v1/users/acl/user-roles — List user-role assignments
GET /v1/users/acl/users/{userId}/roles — List one user's roles
POST /v1/users/acl/users/{userId}/roles — Add roles to a user
PUT /v1/users/acl/users/{userId}/roles — Replace a user's full role set
DELETE /v1/users/acl/users/{userId}/roles/{roleId} — Remove one role assignment
GET /v1/users/acl/role-queries — List role queries
GET /v1/users/acl/role-queries/{roleQueryId} — Get one role query
POST /v1/users/acl/role-queries — Create role query
PATCH /v1/users/acl/role-queries/{roleQueryId} — Update role query
DELETE /v1/users/acl/role-queries/{roleQueryId} — Delete role query
ACL routes require the normal authenticated API session, but they may not appear in generated Swagger/OpenAPI output because the backend currently excludes them from public docs. Integrate them with direct `RestApiClient` calls when you need record sharing, role CRUD, user-role assignment management, or role-query management.
For all ACL role operations, prefer using role `uid` values returned by the API. Nested role objects expose both `id` and `uid`, and both map to the role UID value.
### Making API Calls
// List items with query payload
const items = await client.get('/v1/apps/base/data-sources/project/items', {
columns: 'name, status, record_owner(firstname,lastname)',
filters: { rules: [{ field: 'status', operator: '!=', value: 'archived' }] },
orderBy: 'created_on DESC',
limit: 50,
})
// Get single item
const item = await client.get('/v1/apps/base/data-sources/project/items/uuid-here', {
columns: 'name, description, status',
})
// Create
const newItem = await client.post('/v1/apps/base/data-sources/project/items', {
name: 'New Project',
status: 'status-enum-id',
})
// Update
await client.patch('/v1/apps/base/data-sources/project/items/uuid-here', {
name: 'Updated Name',
})
// Delete
await client.delete('/v1/apps/base/data-sources/project/items/uuid-here')