Aamu exposes a project API for teams and AI agents that need to create, read, and update real work inside Aamu. The API follows the same building blocks people use in the UI: tasks, docs, meetings, forms, files, databases, database automations, and database rows through GraphQL.
The API is described by an OpenAPI document at /.well-known/openapi.json. That document is useful both for humans and for AI tools that need to discover available operations.
Authentication and project scope
Every API request uses a Team API key in the x-api-key header. Project-scoped resources also use x-project-id. When an API key has access to multiple projects, the project header disambiguates which project the request should use.
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_IDAPI keys can be scoped by feature and permission. For example, one key can have read-only Docs access, while another can create tasks, upload files, submit forms, and manage database automations in selected projects.
Database schema and row access use the Database scope. Automation definitions use a separate Automations scope, because permission to work with database data should not automatically grant permission to create workflows with side effects. Automation actions also require the scope of their destination: Tasks write for create_task and Emails write for send_email.
Team admins create keys from Team settings under API keys. The generated secret is shown only once, so copy it immediately. The stored key list later shows safe metadata such as name, creation time, last-used time, scopes, permissions, endpoints and required headers, but it never shows the secret again.
The API documentation links are also available from the same settings page: /.well-known/openapi.json, the alternative /api/openapi.json, and the AI plugin manifest at /.well-known/ai-plugin.json.
API actor
Write operations can set the acting user with x-aamu-actor. The value can be a username, such as ai or badding, or a user id. The actor must be a member of the scoped project.
When the header is omitted, Aamu uses the project ai user when available, otherwise the project owner. The same fallback applies across project item writes: tasks, docs, meetings, form submissions, file upload registration, and database creation/schema operations.
x-aamu-actor: aiAPI key introspection
Integrations can inspect the API key they are currently using with GET /api/v1/key. This is useful for setup checks, debugging missing permissions, and choosing the right project header when a key has multiple scopes.
The endpoint returns safe metadata only. It never returns the API key value, salt, hash, or any other secret used to validate the key.
GET: inspect current API key
GET /api/v1/key
x-api-key: YOUR_API_KEYExample response:
{
"key": {
"id": "API_KEY_ID",
"name": "Support automation",
"permissions": ["read", "write", "comment"],
"scopes": [
{
"feature": "helpdesk",
"resource_id": "PROJECT_ID",
"permissions": ["read", "write", "comment"]
},
{
"feature": "team-brain",
"resource_id": "PROJECT_ID",
"permissions": ["read", "write", "comment"]
}
],
"created": 1780000000000,
"created_by": "USER_ID",
"last_used_at": 1780000000000,
"expires_at": null
}
}Use the returned scopes list to confirm which features the key can access. Project-scoped endpoints still use x-project-id when the project needs to be disambiguated.
Team Brain
Team Brain is the shared knowledge layer behind Aamu AI. It can be queried directly through the API when an integration needs grounded context before it writes a task, drafts a support reply, creates a doc, or decides what to do next.
The retrieve endpoint returns matching curated Team Brain entries and source chunks. It does not generate a final answer by itself; callers can use the results as context for their own model, or let Aamu use the same knowledge through a workflow such as Helpdesk reply-draft generation.
POST: retrieve knowledge
limit controls the maximum number of matching results returned. Results are ranked by relevance score, so a smaller limit is useful when an AI prompt needs only the strongest few pieces of context, while a larger limit gives the caller more material to inspect or rerank.
POST /api/v1/team-brain/retrieve
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_ID
Content-Type: application/json
{
"query": "How should we answer a billing cancellation question?",
"limit": 8
}Example response:
{
"results": [
{
"kind": "brain",
"score": 0.82,
"title": "Cancellation billing policy",
"text": "Explain the policy clearly and ask for the account email if needed.",
"urls": []
}
]
}Use Team Brain read scope for the project. When Helpdesk draft generation is configured to use Team Brain, the same Team Brain read scope is required in addition to Helpdesk write scope.
Users
The Users API resolves project members for actor headers, task assignees and integrations that need stable Aamu user ids. It accepts any project read scope, such as Tasks, Docs, Helpdesk or Emails.
GET: list users
GET /api/v1/users/
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_IDThe older ?username=... query filter is still supported for backwards compatibility, but one-user lookups should use the resource endpoint below.
GET: get one user
GET /api/v1/users/badding
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_IDThe path value can be a username or user id. Example response:
{
"user": {
"id": "USER_ID",
"username": "badding",
"name": "Badding",
"email": "badding@example.com"
}
}Helpdesk
The Helpdesk API is designed for human-in-the-loop support automation. Integrations can read new tickets, prepare reply drafts, and send a draft only through an explicit send command.
Reply drafts are stored in the same user-specific comment draft location the UI uses, and the ticket is marked with hasDraft. In practice this means the draft appears in the Helpdesk reply editor for the API actor, ready for a human to review and send.
GET: list tickets
GET /api/v1/helpdesk/tickets/?status=open&unanswered=true
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_IDGET: resolve a Helpdesk actor
Use this endpoint to check a Helpdesk actor and the project Helpdesk mailbox used for email tickets. The actor can be a username or user id. The response includes safe mailbox metadata only, never passwords or tokens.
GET /api/v1/helpdesk/actors/ile
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_IDExample response:
{
"actor": {
"id": "USER_ID",
"username": "ile",
"name": "Ilkka Huotari",
"email": "user-account@example.com",
"helpdesk_name": "Ilkka",
"mailbox": {
"email": "support@example.com",
"name": "Support",
"configured": true
}
}
}PUT: write a reply draft
PUT /api/v1/helpdesk/tickets/TICKET_ID/reply-draft
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_ID
x-aamu-actor: ai
Content-Type: application/json
{
"html": "<p>Hei, kiitos viestistä. Tarkistin tilanteen ja...</p>",
"mode": "replace"
}POST: generate a reply draft
Aamu can also generate the draft with Team AI. By default this uses Team Brain retrieval as context, so the API key needs both Helpdesk write scope and Team Brain read scope for the project.
POST /api/v1/helpdesk/tickets/TICKET_ID/reply-draft/generate
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_ID
x-aamu-actor: ai
Content-Type: application/json
{
"instructions": "Answer in Finnish, friendly and concise.",
"use_team_brain": true,
"mode": "replace"
}The generated draft is saved as the API actor’s Helpdesk comment draft. It is not sent automatically.
POST: send a reply draft
Sending is deliberately separate from writing or generating a draft. The send endpoint takes the current draft for the API actor, sends it through the same Helpdesk comment/email path as the UI, clears the draft, and returns the sent comment plus the updated ticket.
Use Helpdesk comment permission for this endpoint. x-aamu-actor selects whose draft is sent, so integrations can safely keep AI-generated drafts under an ai actor or send as a specific user when that is intended.
POST /api/v1/helpdesk/tickets/TICKET_ID/reply-draft/send
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_ID
x-aamu-actor: aiExample response:
{
"comment_path": "comments.3",
"comment": {
"id": "COMMENT_ID",
"html": "<p>Hei, kiitos viestistä...</p>",
"from": "ai"
},
"ticket": {
"id": "TICKET_ID",
"has_draft": false
}
}Emails
The Email API follows the same human-in-the-loop model as Helpdesk: integrations can list email threads, write or generate a user-specific reply draft, and send that draft only through an explicit send command.
Drafts are stored in the same user-specific comment draft location the email UI uses and the email is marked with hasDraft. Use x-aamu-actor to select whose draft is written, generated or sent.
GET: list emails
limit controls the maximum number of email threads returned. Use status=unanswered or unanswered=true when building an AI reply queue.
GET /api/v1/emails/?status=unanswered&limit=20
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_IDGET: resolve an email actor
Use this endpoint to check which project-specific email address an actor uses when sending email replies. The actor can be a username or user id. The response includes safe mailbox metadata only, never passwords or tokens.
GET /api/v1/emails/actors/ile
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_IDExample response:
{
"actor": {
"id": "USER_ID",
"username": "ile",
"name": "Ilkka Huotari",
"email": "user-account@example.com",
"mailbox": {
"email": "project-mailbox@example.com",
"name": "Kansalaiskeskustelu",
"configured": true
}
}
}GET: search email contacts
Use contacts when selecting recipients for new drafts or sent emails. The API also auto-resolves address-only recipients to existing contacts by email address. Missing contacts are created only when create_missing_contacts is explicitly set to true.
GET /api/v1/emails/contacts/?q=ilkkah&limit=10
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_ID
x-aamu-actor: ilePOST: create a new email draft
Use this endpoint to create a new outbound email draft for the API actor without sending it. The draft appears in the same email draft list the UI uses. Use Emails write permission for this endpoint.
POST /api/v1/emails/drafts/
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_ID
x-aamu-actor: ile
Content-Type: application/json
{
"subject": "Draft from Aamu API",
"html": "<p>This draft has not been sent.</p>",
"to": [
{ "address": "ilkkah@gmail.com", "name": "Ilkka" }
],
"create_missing_contacts": true
}POST: send a new email
Use this endpoint to send a new outbound email, not a reply to an existing thread. It creates a draft for the API actor, sends it through the same project email path as the UI, and returns the sent email thread. Use Emails comment permission for this endpoint.
POST /api/v1/emails/
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_ID
x-aamu-actor: ile
Content-Type: application/json
{
"subject": "Test email from Aamu API",
"html": "<p>Hello from the Aamu Email API.</p>",
"to": [
{ "address": "ilkkah@gmail.com", "name": "Ilkka" }
]
}PUT: write a reply draft
The API chooses the original sender/contact as the default recipient when possible. You can pass to explicitly if the integration wants to override recipients.
PUT /api/v1/emails/EMAIL_ID/reply-draft
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_ID
x-aamu-actor: ai
Content-Type: application/json
{
"html": "<p>Hei, kiitos viestistä. Palaamme tähän pian.</p>",
"mode": "replace",
"to": [
{ "address": "customer@example.com", "name": "Customer" }
]
}POST: generate a reply draft
Email draft generation supports optional instructions for tone, language or constraints. By default it uses Team Brain retrieval as context, so the API key needs both Emails write scope and Team Brain read scope for the project.
POST /api/v1/emails/EMAIL_ID/reply-draft/generate
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_ID
x-aamu-actor: ai
Content-Type: application/json
{
"instructions": "Answer in Finnish and keep it short.",
"use_team_brain": true,
"mode": "replace"
}POST: send a reply draft
Sending uses the same email-comment/send path as the UI. The endpoint sends the current draft for the API actor, clears it, and returns the sent comment plus the updated email thread. Use Emails comment permission for this endpoint.
POST /api/v1/emails/EMAIL_ID/reply-draft/send
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_ID
x-aamu-actor: aiTasks
The Tasks API is useful for turning external events, AI plans, and support workflows into actionable work. Task create and update operations use the same internal task helpers as the UI for fields that have side effects, such as status, assigned users, dates, repetition, and reminders.
GET: list tasks
GET /api/v1/tasks/
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_IDExample response:
{
"tasks": [
{
"id": "TASK_ID",
"pid": "YOUR_PROJECT_ID",
"title": "Review API integration",
"html": "<p>Check the rollout notes.</p>",
"status": "active",
"start_at": 1779872400000,
"end_at": 1779876000000,
"repeat": "weekly",
"reminders": [
{ "d": 0, "h": 0, "m": 15 }
],
"users": ["USER_ID"],
"comments": []
}
]
}GET: list project users
Use the Users endpoint to resolve usernames to user ids before assigning task users or choosing an API actor.
GET /api/v1/users/
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_IDYou can also filter by exact username:
GET /api/v1/users/?username=badding
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_IDExample response:
{
"users": [
{
"id": "USER_ID",
"username": "badding",
"name": "badding",
"email": "person@example.com"
}
]
}POST: create a task
Create accepts the same task workflow fields as update. If users is provided, the API applies the assignment changes through the same task user helpers as the UI. Dates, repetition, and reminders go through the task date-change helper.
POST /api/v1/tasks/
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_ID
x-aamu-actor: ai
Content-Type: application/json
{
"title": "Prepare customer summary",
"html": "<p>Summarize the latest feedback and add next steps.</p>",
"status": "active",
"users": ["USER_ID_1", "USER_ID_2"],
"start_at": "2026-05-27T09:00:00.000Z",
"end_at": "2026-05-27T10:00:00.000Z",
"reminders": [
{ "minutes_before": 15 },
{ "d": 0, "h": 1, "m": 0 }
],
"comments": [
{ "html": "<p>Created from the external workflow.</p>" }
]
}For repeating tasks, set repeat or repetition to daily, weekly, monthly, or yearly. Leave it out for a one-time task.
Example response:
{
"task": {
"id": "TASK_ID",
"pid": "YOUR_PROJECT_ID",
"title": "Prepare customer summary",
"html": "<p>Summarize the latest feedback and add next steps.</p>",
"status": "active",
"start_at": 1779872400000,
"end_at": 1779876000000,
"reminders": [
{ "d": 0, "h": 0, "m": 15 },
{ "d": 0, "h": 1, "m": 0 }
],
"users": ["USER_ID_1", "USER_ID_2"],
"comments": []
}
}PATCH: update a task
Update supports title, HTML, status, assigned users, start and end dates, repetition, and reminders. Fields with side effects are routed through the same internal helpers as UI operations.
PATCH /api/v1/tasks/TASK_ID
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_ID
x-aamu-actor: ai
Content-Type: application/json
{
"title": "Prepare customer summary and next steps",
"html": "<p>Summarize feedback, risks, and next actions.</p>",
"status": "complete",
"users": ["USER_ID_1", "USER_ID_2"],
"start_at": 1779872400000,
"end_at": 1779876000000,
"repeat": "weekly",
"reminders": [
{ "minutes_before": 15 }
]
}Use null to clear start_at, end_at, repeat, or reminders. Use an empty reminder array to remove all reminders.
Example response:
{
"task": {
"id": "TASK_ID",
"title": "Prepare customer summary and next steps",
"html": "<p>Summarize feedback, risks, and next actions.</p>",
"status": "complete",
"start_at": 1779872400000,
"end_at": 1779876000000,
"repeat": "weekly",
"reminders": [
{ "d": 0, "h": 0, "m": 15 }
],
"users": ["USER_ID_1", "USER_ID_2"]
}
}Task comments
Task comments use the same HTML-oriented content model as tasks. Comments are returned as part of the task response.
GET: read a task with comments
GET /api/v1/tasks/TASK_ID
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_IDExample response:
{
"task": {
"id": "TASK_ID",
"title": "Prepare customer summary",
"html": "<p>Summarize the latest feedback.</p>",
"comments": [
{
"id": "COMMENT_ID",
"html": "<p>Draft summary added.</p>"
}
]
}
}POST: add a comment
POST /api/v1/tasks/TASK_ID/comments
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_ID
Content-Type: application/json
{
"html": "<p>I reviewed the customer notes and added follow-up actions.</p>"
}Example response:
{
"task": {
"id": "TASK_ID",
"comments": [
{
"id": "COMMENT_ID",
"html": "<p>I reviewed the customer notes and added follow-up actions.</p>"
}
]
}
}Docs
The Docs API creates durable written material directly into Aamu. It is a good fit for AI-generated summaries, runbooks, meeting notes, release notes, customer handoff documents, and internal knowledge articles.
GET: list docs
GET /api/v1/docs/
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_IDExample response:
{
"docs": [
{
"id": "DOC_ID",
"pid": "YOUR_PROJECT_ID",
"title": "Weekly API Report",
"status": "public",
"html": ""
}
]
}POST: create a doc
POST /api/v1/docs/
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_ID
Content-Type: application/json
{
"title": "Weekly API Report",
"html": "<h1>Weekly API Report</h1><p>All checks passed.</p>"
}Example response:
{
"doc": {
"id": "DOC_ID",
"pid": "YOUR_PROJECT_ID",
"title": "Weekly API Report",
"status": "public",
"html": "<h1>Weekly API Report</h1><p>All checks passed.</p>"
}
}PATCH: update a doc
PATCH /api/v1/docs/DOC_ID
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_ID
Content-Type: application/json
{
"title": "Weekly API Report, revised",
"html": "<h1>Weekly API Report</h1><p>All checks passed after the retry.</p>"
}Example response:
{
"doc": {
"id": "DOC_ID",
"title": "Weekly API Report, revised",
"html": "<h1>Weekly API Report</h1><p>All checks passed after the retry.</p>"
}
}Meetings
The Meetings API can create and update project meetings. It supports fields such as name, HTML description, start time, end time, and invitee emails.
GET: list meetings
GET /api/v1/meetings/
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_IDExample response:
{
"meetings": [
{
"id": "MEETING_ID",
"pid": "YOUR_PROJECT_ID",
"name": "API rollout review",
"status": "public",
"html": "<p>Review integration status.</p>",
"start_time": 1779458400000,
"end_time": 1779462000000
}
]
}POST: create a meeting
POST /api/v1/meetings/
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_ID
Content-Type: application/json
{
"name": "API rollout review",
"html": "<p>Review integration status and next steps.</p>",
"start_time": 1779458400000,
"end_time": 1779462000000
}Example response:
{
"meeting": {
"id": "MEETING_ID",
"pid": "YOUR_PROJECT_ID",
"name": "API rollout review",
"status": "public",
"html": "<p>Review integration status and next steps.</p>"
}
}PATCH: update a meeting
PATCH /api/v1/meetings/MEETING_ID
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_ID
Content-Type: application/json
{
"name": "API rollout review, updated",
"html": "<p>Review production results and decide follow-up actions.</p>",
"start_time": 1779462000000,
"end_time": 1779465600000
}Example response:
{
"meeting": {
"id": "MEETING_ID",
"name": "API rollout review, updated",
"html": "<p>Review production results and decide follow-up actions.</p>",
"start_time": 1779462000000,
"end_time": 1779465600000
}
}Forms API
The authenticated Forms API lets integrations create Forms, update their questions and settings, list and inspect Forms, and submit responses. Published Form responses become rows in the connected database table.
POST: create a Form
Use Forms write scope. With publish: true, Aamu creates the backing database and table, maps Form fields to database columns, and publishes the Form.
POST /api/v1/forms/
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_ID
x-aamu-actor: USER_ID_OR_USERNAME
Content-Type: application/json
{
"name": "Contact Form",
"description": "Send us a message.",
"publish": true,
"fields": [
{ "title": "Name", "type": "short_text", "required": true },
{ "title": "Email address", "type": "email", "required": true },
{ "title": "Message", "type": "long_text" }
]
}Supported field types include short and long text, email, number, date/time, choice fields, files, paragraphs, and page breaks. Email fields use a text-backed database column but render as HTML email inputs in the public Form.
GET: list and inspect Forms
GET /api/v1/forms/
GET /api/v1/forms/FORM_ID
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_IDPATCH: update a Form
Update metadata without changing questions, or supply fields to replace the question list. Stable item_id values preserve field identity across updates. Setting publish: true publishes a draft Form or publishes newly added fields into the backing table.
PATCH /api/v1/forms/FORM_ID
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_ID
Content-Type: application/json
{
"description": "Updated contact form.",
"publish": true,
"fields": [
{ "item_id": "name", "title": "Name", "type": "short_text", "required": true },
{ "item_id": "email-address", "title": "Email address", "type": "email", "required": true },
{ "item_id": "message", "title": "How can we help?", "type": "long_text", "required": true }
]
}POST: submit a Form response
POST /api/v1/forms/FORM_ID/submissions
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_ID
Content-Type: application/json
{
"fields": {
"name": "Ada Example",
"email-address": "ada@example.com",
"message": "I would like to hear more."
}
}The response contains the created row id together with the Form, database, and table ids.
Webhooks API
The Webhooks API manages team-level outbound webhooks. A webhook can subscribe to selected Aamu event types, optionally filter project-scoped events to specific projects, and send signed HTTP requests to an external endpoint.
Webhook management uses a team-level Webhooks API-key scope. It does not use x-project-id to choose the webhook owner; project filtering is configured with project_ids in the webhook definition. An empty project list accepts matching events from all projects in the team.
POST: create a webhook
POST /api/v1/webhooks/
x-api-key: YOUR_API_KEY
Content-Type: application/json
{
"name": "Form submissions",
"url": "https://example.com/aamu/webhook",
"events": [
"form.created",
"form.updated",
"form.deleted",
"form.submitted",
"db.row.created",
"db.row.updated"
],
"project_ids": ["PROJECT_ID"],
"enabled": true
}The create response contains the signing secret. Store it securely when the webhook is created: later list and get responses expose only secret_last4, not the full secret.
For Forms integrations, form.created, form.updated, and form.deleted describe the Form lifecycle. A response emits form.submitted and also creates the backing database row, producing db.row.created. A later edit to that response row emits db.row.updated.
List, inspect, update, and delete webhooks
GET /api/v1/webhooks/
GET /api/v1/webhooks/WEBHOOK_ID
PATCH /api/v1/webhooks/WEBHOOK_ID
DELETE /api/v1/webhooks/WEBHOOK_ID
x-api-key: YOUR_API_KEYUse Webhooks read scope to list and inspect definitions. Creating, updating, enabling, disabling, and deleting webhooks requires Webhooks write scope.
GET: inspect delivery logs
GET /api/v1/webhooks/logs?limit=50
x-api-key: YOUR_API_KEYThe logs response contains recent webhook events and delivery attempts. Use it to distinguish an event that was never emitted, an event that did not match a webhook, and a delivery that reached the receiver but returned an error.
Each delivery includes an HMAC SHA-256 signature in x-aamu-signature, together with event and delivery ids. Verify the signature against the raw request body before trusting the payload. For a fuller event overview and signature example, see Outbound webhooks in Aamu.app.
Public forms
Public browser forms use the same URL for viewing and submitting. They do not use a Team API key. This is separate from the authenticated Forms API.
GET: render a public form
GET /shared/form/FORM_IDExample response is HTML:
<form action="https://your-team.aamu.app/shared/form/FORM_ID" method="post" enctype="multipart/form-data">
<input type="hidden" name="form_builder" value="1">
...
</form>POST: submit a public form
POST /shared/form/FORM_ID
Content-Type: multipart/form-data
form_builder=1
email=person@example.com
message=I would like to hear more.Example response is usually a redirect to the form thank-you page. For AJAX-style multipart submissions, the response can be JSON:
{
"success": true,
"id": "ROW_ID"
}Files
Aamu’s editor uses relative file URLs such as /file/browser/{filepointer_id}/{file_version_id}/{name}. The Files API follows the same pattern, so API-created content works with the UI editor.
GET: read file metadata
GET /api/v1/files/FILEPOINTER_ID
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_IDExample response:
{
"file": {
"id": "FILEPOINTER_ID",
"filepointer_id": "FILEPOINTER_ID",
"file_id": "FILE_VERSION_ID",
"file_version_id": "FILE_VERSION_ID",
"name": "example.png",
"type": "image/png",
"size": 12345,
"browser_url": "/file/browser/FILEPOINTER_ID/FILE_VERSION_ID/example.png",
"download_url": "/file/dl/FILEPOINTER_ID/FILE_VERSION_ID/example.png"
}
}POST: upload a file
Uploading uses two API POSTs and one signed PUT. First prepare the upload:
POST /api/v1/files/prepare-upload
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_ID
Content-Type: application/json
{
"name": "example.png",
"type": "image/png",
"size": 12345
}Example response:
{
"upload": {
"method": "PUT",
"url": "SIGNED_UPLOAD_URL",
"headers": { "Content-Type": "image/png" }
},
"complete": {
"file_id": "FILE_VERSION_ID",
"bucket": "files",
"key": "UPLOAD_KEY",
"name": "example.png",
"type": "image/png",
"size": 12345
}
}After uploading the bytes to SIGNED_UPLOAD_URL, complete the upload:
POST /api/v1/files/complete-upload
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_ID
Content-Type: application/json
{
"file_id": "FILE_VERSION_ID",
"bucket": "files",
"key": "UPLOAD_KEY",
"name": "example.png",
"type": "image/png",
"size": 12345
}Example response:
{
"file": {
"id": "FILEPOINTER_ID",
"file_id": "FILE_VERSION_ID",
"browser_url": "/file/browser/FILEPOINTER_ID/FILE_VERSION_ID/example.png",
"download_url": "/file/dl/FILEPOINTER_ID/FILE_VERSION_ID/example.png"
}
}The browser_url can be embedded in Docs or Tasks HTML. The old item-level files field is deprecated and is not returned by the current API.
Databases and GraphQL
GraphQL is the main database row API. The REST Database API creates databases, adds tables, and changes table schema. Row reads and writes then use the generated GraphQL schema for that database.
A useful mental model is: use REST to shape the database, and use GraphQL to work with the rows inside it. This keeps schema setup explicit while giving integrations a typed query and mutation surface for actual data.
POST: create a database
Creating a database also creates its first table. The response returns both the database id and the initial table id.
POST /api/v1/databases/
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_ID
Content-Type: application/json
{
"name": "Small-team CRM"
}Example response:
{
"database": {
"id": "DB_ID",
"pid": "YOUR_PROJECT_ID",
"name": "Small-team CRM",
"tables": ["DEALS_TABLE_ID"],
"table_id": "DEALS_TABLE_ID"
}
}POST: add a table
Use the table endpoint when the integration needs a related table, for example companies next to deals, contacts next to accounts, or products next to orders.
POST /api/v1/databases/DB_ID/tables
x-api-key: YOUR_API_KEY
Content-Type: application/json
{
"name": "Companies",
"gtype": "Companies"
}Example response:
{
"table": {
"id": "COMPANIES_TABLE_ID",
"name": "Companies",
"gtype": "Companies",
"columns": []
},
"database": {
"id": "DB_ID",
"tables": ["DEALS_TABLE_ID", "COMPANIES_TABLE_ID"]
}
}POST: add columns
Columns are added to one table at a time. A reference column links rows in the current table to rows in another table in the same database.
The public column types are text, longtext, link, document, documents, number, status, checkbox, timedate, timeline, tags, file, files, reference, contact, user, task, tasks, email, emails, meeting, and meetings. List-like fields such as documents, tags, tasks, emails, and meetings are written as arrays in GraphQL.
POST /api/v1/databases/DB_ID/tables/COMPANIES_TABLE_ID/columns
x-api-key: YOUR_API_KEY
Content-Type: application/json
{
"columns": [
{ "name": "Company name", "type": "text", "gtype": "companyName" },
{ "name": "Website", "type": "link", "gtype": "website" },
{ "name": "Customer docs", "type": "documents", "gtype": "customerDocs" }
]
}Then add fields to the first table and link each deal to a company:
POST /api/v1/databases/DB_ID/tables/DEALS_TABLE_ID/columns
x-api-key: YOUR_API_KEY
Content-Type: application/json
{
"columns": [
{ "name": "Deal name", "type": "text", "gtype": "dealName" },
{ "name": "Stage", "type": "status", "gtype": "stage" },
{
"name": "Company",
"type": "reference",
"gtype": "company",
"options": {
"type": "one_to_one",
"table": "COMPANIES_TABLE_ID",
"column_show": "companyName"
}
}
]
}options.type can be one_to_one or one_to_many. options.table points to the target table, and options.column_show points to the target column shown as the reference label. The target can be either the column id or its gtype.
document receives one existing Aamu Doc id. documents receives an array of existing Doc ids. The API links those Doc cells back to the database row, so create or resolve the Docs first instead of sending placeholder ids.
Reference columns currently target another table. Same-table references are rejected because the generated database GraphQL schema does not support circular references yet. automatic_reference is also not part of the public API yet.
POST: inspect the generated GraphQL schema
The exact query and mutation names come from the generated schema. Use introspection after creating or changing schema, especially when an integration creates its own table gtype values.
POST /api/v1/graphql/
x-api-key: YOUR_API_KEY
x-db-id: DB_ID
Content-Type: application/json
{
"query": "{ __schema { mutationType { fields { name } } queryType { fields { name } } } }"
}POST: add row data with GraphQL
Create target rows first, then store the referenced row id in the reference field. A one_to_one reference receives one row id. A one_to_many reference receives an array of row ids.
A GraphQL insert uses the same row-inserted automation path as Forms API and UI-created rows. If the table has a public row_inserted automation, its task or email actions run after the row is stored.
POST /api/v1/graphql/
x-api-key: YOUR_API_KEY
x-db-id: DB_ID
Content-Type: application/json
{
"query": "mutation CreateCompany($companyName: String, $website: String, $customerDocs: [String]) { Companies(companyName: $companyName, website: $website, customerDocs: $customerDocs) { id companyName website customerDocs } }",
"variables": {
"companyName": "Acme Ltd",
"website": "https://example.com",
"customerDocs": ["DOC_ID_1", "DOC_ID_2"]
}
}Then create a deal and pass the company row id to the reference column:
POST /api/v1/graphql/
x-api-key: YOUR_API_KEY
x-db-id: DB_ID
Content-Type: application/json
{
"query": "mutation CreateDeal($dealName: String, $stage: String, $company: String) { Sheet1(dealName: $dealName, stage: $stage, company: $company) { id dealName stage company { id companyName website } } }",
"variables": {
"dealName": "Website redesign",
"stage": "new",
"company": "COMPANY_ROW_ID"
}
}Example response:
{
"data": {
"Sheet1": {
"id": "DEAL_ROW_ID",
"dealName": "Website redesign",
"stage": "new",
"company": {
"id": "COMPANY_ROW_ID",
"companyName": "Acme Ltd",
"website": "https://example.com",
"customerDocs": ["DOC_ID_1", "DOC_ID_2"]
}
}
}
}GET: read row data with GraphQL
GraphQL reads are sent as HTTP POST requests to /api/v1/graphql/. This is normal GraphQL behavior: the operation is a read, even though the HTTP method is POST.
POST /api/v1/graphql/
x-api-key: YOUR_API_KEY
x-db-id: DB_ID
Content-Type: application/json
{
"query": "query { Sheet1Rows { id dealName stage company { id companyName website } } }"
}POST: update row data with GraphQL
GraphQL updates are also sent to /api/v1/graphql/ with HTTP POST. The mutation name depends on the generated schema, so use introspection to confirm the exact name and input type.
POST /api/v1/graphql/
x-api-key: YOUR_API_KEY
x-db-id: DB_ID
Content-Type: application/json
{
"query": "mutation UpdateDeal($id: ID!, $dealName: String, $stage: String, $company: String) { updateSheet1(id: $id, dealName: $dealName, stage: $stage, company: $company) { id dealName stage company { id companyName } } }",
"variables": {
"id": "DEAL_ROW_ID",
"dealName": "Website redesign",
"stage": "won",
"company": "COMPANY_ROW_ID"
}
}GET: read database activity
Database activity records describe cell-level row changes. They are useful for row detail timelines, audit views, CRM history, and integrations that need to react to field changes after data has been written through the UI or API.
GET /api/v1/databases/DB_ID/activity?table_id=DEALS_TABLE_ID&row_id=DEAL_ROW_ID&limit=20
x-api-key: YOUR_API_KEYExample response:
{
"activity": [
{
"id": "ACTIVITY_ID",
"dbid": "DB_ID",
"tableid": "DEALS_TABLE_ID",
"rowid": "DEAL_ROW_ID",
"dataid": "CELL_DATA_ID",
"colid": "stage",
"op": "update",
"oldvalue": "new",
"newvalue": "won",
"userId": "USER_ID",
"created": 1780000000000,
"render": {
"field": { "id": "stage", "name": "Stage", "type": "status" },
"old_display": "New",
"new_display": "Won",
"summary": "Stage changed from New to Won"
}
}
]
}op is insert, update or delete. oldvalue and newvalue keep the stored value shape for the column: strings for text/status/reference values, arrays for list-like columns, and objects for structured columns such as timeline and file. The optional render object is a convenience summary for display.
Database Automations API
The Automations API manages workflow definitions attached to a database. It supports listing and creating automations under a database, then reading, updating, or deleting an individual automation by id.
GET /api/v1/databases/DB_ID/automations
POST /api/v1/databases/DB_ID/automations
GET /api/v1/automations/AUTOMATION_ID
PATCH /api/v1/automations/AUTOMATION_ID
DELETE /api/v1/automations/AUTOMATION_IDThese endpoints use x-api-key. They resolve the project from the database, so they do not need x-project-id. Use Automations read permission for list/get and Automations write permission for create/update/delete.
POST: create an automation
POST /api/v1/databases/DB_ID/automations
x-api-key: YOUR_API_KEY
Content-Type: application/json
{
"name": "Create follow-up task",
"status": "public",
"trigger": {
"type": "row_inserted",
"tableId": "TABLE_ID"
},
"actions": [
{
"type": "create_task",
"pid": "PROJECT_ID",
"users": ["USER_ID"],
"dbFieldTitle": "TITLE_COLUMN_ID",
"dbFieldBody": "BODY_COLUMN_ID"
}
]
}Supported trigger types are row_inserted and row_updated. A row-updated trigger also specifies columnId and the target value:
{
"name": "Start delivery when deal is won",
"status": "public",
"trigger": {
"type": "row_updated",
"tableId": "DEALS_TABLE_ID",
"columnId": "STAGE_COLUMN_ID",
"value": "won"
},
"actions": [
{
"type": "create_task",
"pid": "DELIVERY_PROJECT_ID",
"dbFieldTitle": "DEAL_NAME_COLUMN_ID"
}
]
}The update automation runs only when the selected field changes to the configured value. The supported actions are create_task and send_email. Action projects are validated separately, so the key needs Tasks write or Emails write scope for each target project.
GET and PATCH an automation
GET /api/v1/automations/AUTOMATION_ID
x-api-key: YOUR_API_KEY
PATCH /api/v1/automations/AUTOMATION_ID
x-api-key: YOUR_API_KEY
Content-Type: application/json
{
"status": "draft",
"name": "Paused follow-up task"
}A partial PATCH keeps fields that are not included. Switching the status to draft pauses execution without deleting the definition. DELETE removes the automation from API and UI listings.
Forms, GraphQL, automations, and Tasks together
These APIs can form one end-to-end workflow:
Create a database and columns with the Database REST API.
Create a public automation with Automations write scope.
Submit a form through
/api/v1/forms/{id}/submissionsor insert a row through GraphQL.The row is stored in the database and the matching automation runs.
A task is created in its configured project or an email action runs.
This lets an integration keep structured input in a database while turning relevant events into visible work for the team. See Database automations with Aamu.app for the product-side walkthrough.
Using Databases and Docs together
Docs and Databases work well together. A database can hold structured state, while Docs can hold rich long-form context. The bridge between the two is the document column type: its value is the id of a Docs document.
GET: fetch a linked Doc after querying a row
GET /api/v1/docs/DOC_ID
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_IDExample response:
{
"doc": {
"id": "DOC_ID",
"title": "Interview notes: Ada Lovelace",
"html": "<h1>Interview notes</h1><p>Ada liked the onboarding flow.</p>"
}
}POST: create a Doc and store its id in a row
First create the document:
POST /api/v1/docs/
x-api-key: YOUR_API_KEY
x-project-id: YOUR_PROJECT_ID
Content-Type: application/json
{
"title": "Interview notes: Ada Lovelace",
"html": "<h1>Interview notes</h1><p>Ada liked the onboarding flow and asked for more examples.</p>"
}Example response:
{
"doc": {
"id": "DOC_ID",
"title": "Interview notes: Ada Lovelace"
}
}Then use that DOC_ID in a database row whose column type is document:
POST /api/v1/graphql/
x-api-key: YOUR_API_KEY
x-db-id: DB_ID
Content-Type: application/json
{
"query": "mutation CreateFeedback($input: FeedbackInput!) { createFeedback(input: $input) { id customer sourceDocument } }",
"variables": {
"input": {
"customer": "Ada Lovelace",
"sourceDocument": "DOC_ID"
}
}
}Example response:
{
"data": {
"createFeedback": {
"id": "ROW_ID",
"customer": "Ada Lovelace",
"sourceDocument": "DOC_ID"
}
}
}Legacy direct table submit
Older generated database form snippets can submit directly to a table endpoint. This creates database rows, but it is best treated as a legacy/browser form integration.
GET: no row listing endpoint here
There is no recommended GET endpoint for this legacy table-submit surface. To read rows, use GraphQL instead.
POST /api/v1/graphql/
x-api-key: YOUR_API_KEY
x-db-id: DB_ID
Content-Type: application/json
{
"query": "query { feedbackRows { id customer message } }"
}Example response:
{
"data": {
"feedbackRows": [
{ "id": "ROW_ID", "customer": "Ada Lovelace", "message": "The onboarding flow was clear." }
]
}
}POST: legacy table submit
POST /api/v1/db/TABLE_ID
Content-Type: application/x-www-form-urlencoded
customer=Ada%20Lovelace&message=The%20onboarding%20flow%20was%20clear.Example response:
{
"success": true,
"id": "ROW_ID"
}New authenticated integrations should prefer /api/v1/forms/{id}/submissions for form submissions and /api/v1/graphql/ for database row operations.
Update support summary
Update support is intentionally feature-specific. Tasks, Docs, and Meetings expose REST PATCH endpoints. Database rows are updated through GraphQL mutations. Forms submissions and public form posts create rows; they are not update endpoints. Files are currently uploaded and fetched through the Files API, while replacing or deleting files should be modeled explicitly by the feature that references them.
Why this works well for AI agents
The Aamu API is intentionally close to the product model. An AI agent can create a task, attach files, write a doc, schedule a meeting, submit a form response, manage a database automation, or work with structured database rows without inventing a parallel workflow.
The OpenAPI document gives the agent a map of the available operations, while scoped Team API keys keep access narrow. That combination makes the API useful for automation without making it too broad by default.
A practical starting point
For most integrations, the best first step is to generate a Team API key with the smallest useful set of project and feature scopes. Then call the OpenAPI document, inspect the schemas, and start with one workflow: create a task, write a doc, or upload a file and reference it from editor HTML.
From there, the same API surface can grow into richer workflows that use tasks for action, docs for knowledge, forms for input, files for context, meetings for coordination, GraphQL databases for structured state, and automations for the handoff between data and action.
How the API operation surface guides Aamu's internal AI
The public API remains the integration surface for external systems, API keys, and project-scoped automation. Aamu's internal AI can now work with the same described operation set when a user asks it to act inside the workspace.
The OpenAPI operation names and request schemas help the AI select an operation and prepare its path, query, and body arguments. The internal action does not need an API key and does not have to call Aamu over HTTP: it reuses the existing server-side handlers and product functions with the signed-in user's session and accessible projects.
This keeps the public API and internal AI capabilities aligned across areas such as Forms, databases, GraphQL rows, database automations, files, users, Helpdesk, Email, Docs, Tasks, and Meetings. External integrations still use API key scopes; internal AI follows the current user's workspace permissions. Higher-risk actions retain explicit confirmation boundaries.
