بنقرة واحدة
atoms-api
Generate REST APIs from api-config.json for TypeScript or Rust backends
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Generate REST APIs from api-config.json for TypeScript or Rust backends
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
| name | atoms-api |
| description | Generate REST APIs from api-config.json for TypeScript or Rust backends |
| version | 1.0 |
| author | scoutos |
Generate api-config.json files for backend-only atoms-kit applications.
This is a backend-only variant of atoms-kit that defines just the data layer:
No frontend (pages, navigation, theme) is defined — this is purely for generating a REST API layer.
A simple task API:
{
"version": "1.0",
"app": {
"name": "Task API",
"description": "Simple task management API"
},
"atoms": {
"task": {
"label": "Task",
"icon": "RiCheckboxLine",
"fields": {
"title": { "type": "text", "required": true },
"description": { "type": "longtext" },
"status": {
"type": "enum",
"values": ["todo", "in_progress", "done"],
"default": "todo"
},
"priority": {
"type": "enum",
"values": ["low", "medium", "high"],
"default": "medium"
},
"assigned_to": { "type": "text", "description": "Clerk user ID" },
"due_at": { "type": "datetime" },
"team_id": { "type": "text" }
},
"relations": {
"comments": { "type": "has_many", "atom": "comment", "foreignField": "root_id" }
}
}
}
}
This generates:
Task type/struct with all fields plus id and org_id/api/atoms/task{
$schema?: string // Optional JSON schema reference
version: "1.0" // Required, must be "1.0"
app: AppMeta // App name and description
atoms: Record<string, AtomConfig> // Your data entities
}
{
name: string // App name
description?: string // Short description
}
An atom is a data entity (like a database table).
{
label?: string // Display name (e.g., "Contact")
description?: string // Short description
icon?: IconName // Icon from the icon list (optional)
fields: Record<string, FieldConfig> // Field definitions
relations?: Record<string, RelationConfig> // Optional relations
}
Shorthand form (type only):
"name": "text"
Full object form:
"name": {
"type": "text",
"label": "Name",
"required": true,
"description": "User's full name",
"default": "Untitled"
}
Field types:
| Type | Use Case | Example |
|---|---|---|
text | Short text (names, titles) | { "type": "text" } |
longtext | Multi-line content | { "type": "longtext" } |
email | Email addresses | { "type": "email" } |
url | Web URLs | { "type": "url" } |
number | Numeric values | { "type": "number" } |
boolean | True/false | { "type": "boolean", "default": false } |
datetime | Date/time | { "type": "datetime" } |
json | Arbitrary JSON | { "type": "json" } |
enum | Fixed set of values | See below |
Enum fields:
"status": {
"type": "enum",
"label": "Status",
"values": ["open", "in_progress", "closed"],
"default": "open"
}
{
type: "belongs_to" | "has_many" | "has_one"
atom: string // Target atom name
field?: string // Required for belongs_to
foreignField?: string // Required for has_many/has_one
}
Examples:
"relations": {
"company": {
"type": "belongs_to",
"atom": "company",
"field": "company_id"
},
"deals": {
"type": "has_many",
"atom": "deal",
"foreignField": "contact_id"
}
}
These are automatically available — do not define them in atoms:
| Atom | Purpose |
|---|---|
team | Multi-tenancy teams |
comment | Comments on any atom |
org_settings | Organization configuration |
atom_definition | Dynamic atom schemas |
Every atom automatically includes these fields — do NOT define them manually:
| Field | Type | Description |
|---|---|---|
id | string | Auto-generated unique identifier (UUID) |
org_id | string | Organization ID for multi-tenancy (auto-populated from auth) |
These are added by the API layer on all operations. Your fields definition should only include domain-specific fields.
Atoms-kit supports two levels of data scoping:
User authenticates → Token contains org_id → API filters all data by org_id
?filter[team_id]=xxxteam_id field to atoms that need it// In your atom definition:
"fields": {
"name": { "type": "text" },
"team_id": { "type": "text", "description": "Team this belongs to" }
}
Key difference:
org_id = Hard isolation between companies/accounts (automatic)team_id = Soft grouping within an org (opt-in, manual)All API requests require a Bearer token in the Authorization header:
Authorization: Bearer <token>
Set the SCOUTOS_API_KEY environment variable. The API will:
"default" as the org_idSCOUTOS_API_KEY=sk_live_xxx
When using Clerk authentication:
userId and orgId are extracted from the token| Status | Meaning |
|---|---|
401 | Missing or invalid token |
400 | Valid token but missing org membership |
All API responses use a consistent JSON envelope.
// GET /api/atoms/task/abc123
{
"data": {
"id": "abc123",
"title": "Build API",
"status": "in_progress",
"priority": "high",
"assigned_to": "user_456",
"team_id": "team_789",
"org_id": "org_001"
}
}
// GET /api/atoms/task?limit=10
{
"data": [
{ "id": "abc123", "title": "Build API", ... },
{ "id": "def456", "title": "Write tests", ... }
]
}
Note: The list response returns a flat array under data. Pagination metadata may be added via query params but is not included in the response body.
// POST /api/atoms/task
// Request body: { "title": "New task", "status": "todo" }
{
"data": {
"id": "xyz789",
"title": "New task",
"status": "todo",
"org_id": "org_001"
}
}
// DELETE /api/atoms/task/abc123
{
"success": true
}
{
"error": "Unauthorized"
}
{
"error": "Unknown atom: invalid_atom_name"
}
When implementing the API in TypeScript or Rust, use these type mappings:
| Field Type | TypeScript Type |
|---|---|
text | string |
longtext | string |
email | string |
url | string |
number | number |
boolean | boolean |
datetime | string (ISO 8601) |
json | unknown |
enum | string (union type recommended) |
| Field Type | Rust Type |
|---|---|
text | String |
longtext | String |
email | String |
url | String |
number | i64 |
boolean | bool |
datetime | chrono::DateTime<chrono::Utc> |
json | serde_json::Value |
enum | enum (derive Serialize, Deserialize) |
| Field Type | SQL Type |
|---|---|
text | VARCHAR(255) |
longtext | TEXT |
email | VARCHAR(255) |
url | VARCHAR(2048) |
number | BIGINT |
boolean | BOOLEAN |
datetime | TIMESTAMPTZ |
json | JSONB |
enum | VARCHAR(64) + CHECK constraint |
CREATE INDEX idx_{atom}_org_id ON {atom}(org_id);
CREATE INDEX idx_{atom}_team_id ON {atom}(team_id); -- if team_id exists
CREATE INDEX idx_{atom}_created_at ON {atom}(created_at DESC);
-- Add indexes on frequently filtered enum fields (status, priority, etc.)
The PUT /api/atoms/{atom}/{id} endpoint performs a full replacement, not a partial merge. Include all fields in your request body.
// PUT /api/atoms/task/abc123
// This will remove "assigned_to" if not included:
{ "title": "Updated title", "status": "done" }
// Correct approach — include all fields:
{ "title": "Updated title", "status": "done", "assigned_to": "user_456", ... }
DELETE returns { "success": true } even if the record didn't exist. This allows safe retries.
On POST and PUT:
org_id in request bodyWhen creating a record:
default values from schema are appliednull/missing fields stay nullHere's a minimal OpenAPI 3.0 specification for one atom:
openapi: 3.0.0
info:
title: Task API
version: 1.0.0
paths:
/api/atoms/task:
get:
summary: List tasks
parameters:
- name: filter[status]
in: query
schema:
type: string
enum: [todo, in_progress, done]
- name: filter[team_id]
in: query
schema:
type: string
- name: limit
in: query
schema:
type: integer
default: 50
responses:
'200':
description: List of tasks
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/Task'
post:
summary: Create a task
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TaskCreate'
responses:
'200':
description: Created task
content:
application/json:
schema:
type: object
properties:
data:
$ref: '#/components/schemas/Task'
/api/atoms/task/{id}:
get:
summary: Get a task
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'200':
description: Task details
content:
application/json:
schema:
type: object
properties:
data:
$ref: '#/components/schemas/Task'
'404':
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
schemas:
Task:
type: object
properties:
id:
type: string
title:
type: string
status:
type: string
enum: [todo, in_progress, done]
priority:
type: string
enum: [low, medium, high]
assigned_to:
type: string
nullable: true
team_id:
type: string
nullable: true
org_id:
type: string
TaskCreate:
type: object
required: [title]
properties:
title:
type: string
status:
type: string
enum: [todo, in_progress, done]
default: todo
priority:
type: string
enum: [low, medium, high]
default: medium
assigned_to:
type: string
team_id:
type: string
Error:
type: object
properties:
error:
type: string
securitySchemes:
BearerAuth:
type: http
scheme: bearer
security:
- BearerAuth: []
Choose from these Remix Icon names (optional, for tooling/display purposes):
RiDashboardLine, RiHome4Line, RiSettingsLine, RiSearchLine,
RiNotification3Line, RiInboxLine, RiAddLine, RiFilter2Line,
RiUserLine, RiGroupLine, RiTeamLine, RiUserHeartLine, RiUserSearchLine,
RiContactsBookLine, RiCustomerService2Line,
RiMoneyDollarCircleLine, RiShoppingCartLine, RiPriceTag3Line, RiCouponLine,
RiCheckboxLine, RiTodoLine, RiListCheck2, RiFolderLine, RiBriefcaseLine,
RiFileListLine, RiBookLine,
RiCalendarLine, RiVideoLine, RiTimeLine,
RiMegaphoneLine, RiMailLine, RiChat1Line,
RiBarChartLine, RiPieChartLine,
RiTicketLine,
RiCodeSSlashLine, RiLightbulbLine, RiAtomLine, RiAppsLine, RiDatabase2Line,
RiGlobalLine, RiHashtag, RiLink, RiLockLine, RiStarLine
| Element | Convention | Example |
|---|---|---|
| Atom names | singular, snake_case | contact, kb_article |
| Field names | snake_case | first_name, close_date |
User references (Clerk user IDs):
"owner_id": { "type": "text", "label": "Owner", "description": "Clerk user ID" },
"assigned_to": { "type": "text", "label": "Assigned To", "description": "Clerk user ID" },
"created_by": { "type": "text", "label": "Created By", "description": "Clerk user ID" }
Foreign keys (belongs_to relations):
"company_id": { "type": "text", "label": "Company" },
"project_id": { "type": "text", "label": "Project" }
Standard timestamp fields:
"created_at": { "type": "datetime" },
"updated_at": { "type": "datetime" },
"due_at": { "type": "datetime", "label": "Due Date" },
"resolved_at": { "type": "datetime", "label": "Resolved At" }
Comments on any atom:
"comments": {
"type": "has_many",
"atom": "comment",
"foreignField": "root_id"
}
Belongs_to (child → parent):
"company": {
"type": "belongs_to",
"atom": "company",
"field": "company_id"
}
Has_many (parent → children):
"contacts": {
"type": "has_many",
"atom": "contact",
"foreignField": "company_id"
}
When asked to create a backend API from api-config.json:
Parse the config. Load the JSON and validate:
Generate types. Create TypeScript interfaces or Rust structs:
id, org_id)Generate routes. For each atom, create CRUD endpoints:
GET /api/atoms/{atom} — List with filteringPOST /api/atoms/{atom} — CreateGET /api/atoms/{atom}/{id} — Get onePUT /api/atoms/{atom}/{id} — ReplaceDELETE /api/atoms/{atom}/{id} — DeleteImplement auth middleware. Extract org_id from:
Add multi-tenancy. Every query must filter by org_id, every write must inject it.
| Error | Cause | Fix |
|---|---|---|
atom "X" is not defined | Relation references non-existent atom | Add the atom to atoms |
belongs_to relation must specify "field" | Missing foreign key field | Add "field": "atom_id" |
has_many relation must specify "foreignField" | Missing inverse field | Add "foreignField": "parent_id" |
duplicate atom name "X" | Two atoms with same name | Use unique atom names |
invalid field type "X" | Unknown field type | Use: text, longtext, number, boolean, datetime, json, enum, email, url |
If you have an existing app-config.json and want to extract just the backend:
pages, navigation, and theme sectionscard, list_columns, etc. from pages)version, app, and atomsThe resulting api-config.json will define the same data model for API-only use.
# Validate an api-config.json
atoms validate api-config.json
# Initialize a backend-only project
atoms init ./my-api --config api-config.json