Skip to main content

Public API

Status: Implemented (subscribed tier)
Configure in: Settings → Developer → API Keys
Base URL (production): https://arcos.corgicy.com/api/public/v1
Base URL (local): http://localhost:3001/api/public/v1
OpenAPI: https://arcos.corgicy.com/api/public/v1/openapi.yaml
Swagger UI: https://arcos.corgicy.com/api/public/v1/docs
Related: Outgoing webhooks · MCP · API reference

The Public API is a write-only fitness and lifestyle log for scripts, devices, and automations. It uses API keys (arc_…). It is not the session API the web app uses, and it is not MCP.

It does not read your data, search Brain, manage forms, or push events. For those, use MCP or webhooks.


Choose this surface when​

You want…Use
A Garmin/script/cron to log water, steps, workouts, sleep, mood, cardio, meditation, body weightPublic API (/api/public/v1)
Create / publish / submit Brain forms over HTTPBrain session API on this page (/api/v1/brain/forms)
An assistant to read and write across ARCMCP
ARC to POST to your server when something happens (including Brain forms)Webhooks

Quick start​

  1. Subscribe (API keys require an active plan).
  2. Settings → Developer → create an API key → copy arc_… once.
  3. Call an endpoint:
curl -X POST https://arcos.corgicy.com/api/public/v1/water \
-H "Authorization: Bearer arc_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"volume": 500}'
  1. Confirm the entry on Today in the app.

Interactive docs: Swagger UI.


Authentication​

Every Public API request:

Authorization: Bearer arc_<key>
Content-Type: application/json
RuleDetail
PrefixKeys start with arc_ then 32 random bytes, base64url
StorageARC stores a hash, not the plaintext. You cannot retrieve the full key later
ScopeThe key is tied to your user. It cannot see other accounts
Revoke / rotateSettings UI or /api/v1/api-keys (session auth, not the public key itself)
ExpiryOptional expiresAt on the key

Missing/invalid/revoked/expired key → 401 UNAUTHORIZED.

API keys cannot call /api/v1/webhooks, /api/v1/brain/*, or MCP. Those use a session cookie or OAuth.


Behaviour shared by all write endpoints​

  • Data is attached to today’s ARC Day automatically. You do not send dayId.
  • Optional time / startTime fields are ISO 8601 datetimes.
  • Success envelope: { "ok": true, "data": { … } }.
  • Error envelope: { "ok": false, "error": { "code", "message", "details?" } }.
  • Responses are sanitized (ids + what you logged, not emails or other profile fields).
  • Logging a workout/cardio/water/meditation through this API uses the same services as the app, so matching webhooks can fire (training_logged, cardio_logged, water_logged, meditation_logged).

Endpoints​

All paths below are relative to /api/public/v1.

GET /exercises/search​

The only read endpoint. Use it to resolve exerciseId before POST /workouts.

Query: q (optional search string).

curl "https://arcos.corgicy.com/api/public/v1/exercises/search?q=bench" \
-H "Authorization: Bearer arc_YOUR_KEY"
{
"ok": true,
"data": [
{ "id": "uuid", "name": "Bench press", "muscleGroup": "Chest" }
]
}

Ids are from your exercise catalog, not a global library.

POST /water​

FieldTypeRequiredNotes
volumenumberyesMillilitres, must be positive
timeISO datetimenoDefaults to now
notesstringno
{ "volume": 500, "notes": "After workout" }

POST /workouts​

FieldTypeRequiredNotes
splitLabelstringyese.g. Push, Pull, Legs (your split names)
startTime / endTimeISO datetimeno
sessionRPEint 1–10no
notesstringno
exercisesarrayno{ exerciseId, sets: [{ setNumber, reps?, weight? }] }

exerciseId must be a UUID from GET /exercises/search. setNumber and reps are positive integers; weight is a positive number.

{
"splitLabel": "Push",
"sessionRPE": 7,
"exercises": [
{
"exerciseId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"sets": [{ "setNumber": 1, "reps": 10, "weight": 100 }]
}
]
}

POST /steps​

FieldTypeRequiredNotes
stepsintyesNon-negative
timeISO datetimeno
notesstringno

POST /meditation​

FieldTypeRequiredNotes
durationintyesMinutes, positive
typestringnoFree text, e.g. mindfulness
timeISO datetimeno
notesstringno

POST /cardio​

FieldTypeRequiredNotes
typestringyese.g. Running, Cycling
durationintyesMinutes, positive
intensityenumnoeasy | zone2 | hard (default easy)
distancenumbernoPositive
pacenumbernoPositive
averageHeartRate / maxHeartRateintnoPositive bpm
caloriesintnoPositive
elevationGainintnoNon-negative
startTime / endTimeISO datetimeno
notesstringno

POST /mood​

FieldTypeRequiredNotes
moodint 1–10yes
energyint 1–10yes
notesstringno
timeISO datetimeno

Writes today’s day mood/energy fields.

POST /sleep​

FieldTypeRequiredNotes
durationnumberyesHours, positive
bedtime / wakeTimeISO datetimeno
qualityint 1–10no
notesstringno

Sets today’s sleepHours (and echoes bedtime/wake when provided).

POST /body-metrics​

FieldTypeRequiredNotes
weightnumberyesKilograms, positive
bodyFatnumberno0–100 percent
notesstringno
timeISO datetimenoUsed as the measurement date

Errors​

CodeHTTPMeaning
UNAUTHORIZED401Missing, invalid, expired, or revoked key
VALIDATION_ERROR400Zod schema failed (details.errors[] with path + message)
RATE_LIMIT_EXCEEDED429Too many requests for this key
INTERNAL_ERROR500Server error
{
"ok": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input",
"details": {
"errors": [{ "path": ["volume"], "message": "Expected number, received string" }]
}
}
}

Rate limits​

Default: 500 requests / 15 minutes / API key (env: RATE_LIMIT_PUBLIC_API_MAX, RATE_LIMIT_PUBLIC_API_TIME_WINDOW).

Response headers: x-ratelimit-limit, x-ratelimit-remaining, x-ratelimit-reset, retry-after.

{
"statusCode": 429,
"error": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded for public API. Maximum 500 requests per 900 seconds.",
"retryAfter": 300
}

Managing keys (session API)​

These routes use the logged-in cookie, not arc_…. Same UI: Settings → Developer.

MethodPathNotes
GET/api/v1/api-keysPrefix, last used, expiry, revoked
POST/api/v1/api-keys{ "name": "Watch sync" } → returns key once
POST/api/v1/api-keys/:keyId/revokeImmediate
POST/api/v1/api-keys/:keyId/rotateRevokes old, returns new key once
DELETE/api/v1/api-keys/:keyId

Treat keys like passwords. Do not commit them. Prefer one key per integration so you can revoke a single device.


Examples​

Python

import os, requests

BASE = "https://arcos.corgicy.com/api/public/v1"
headers = {
"Authorization": f"Bearer {os.environ['ARC_API_KEY']}",
"Content-Type": "application/json",
}

r = requests.post(f"{BASE}/water", headers=headers, json={"volume": 500})
r.raise_for_status()
print(r.json())

Node

const BASE = 'https://arcos.corgicy.com/api/public/v1';

async function logMood(mood, energy) {
const res = await fetch(`${BASE}/mood`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.ARC_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ mood, energy }),
});
return res.json();
}

What this API cannot do​

  • Read tasks, Brain notes, finance, people, or forms (arc_… keys cannot call /api/v1/brain/*)
  • Submit or list Brain forms with an API key — use the session Brain HTTP API below, MCP brain_form_*, or the public form URL
  • Register webhook URLs (session + subscription)
  • Replace MCP for assistants

If you log water here and subscribe to water_logged, your webhook URL will receive the event after the write succeeds.


Troubleshooting​

SymptomCheck
401Bearer arc_…, key not revoked, subscription still active
400Types: numbers not strings; ISO dates; intensity exactly easy/zone2/hard
Workout 400 on exerciseIdCall GET /exercises/search first; id must exist in your catalog
429Back off using retry-after
Data on the wrong dayPublic API always uses today in the server’s day logic; it is not a historical import API
Nothing in the appYou are looking at a different user/day; refresh Today

Brain HTTP API (session)​

Brain notes, databases, and forms are not on /api/public/v1. They live on /api/v1/brain/* with the login cookie (same as the web app). MCP brain_* / brain_form_* calls the same services.

Full catalog: HTTP API endpoint catalog. Product: Brain.

Forms (authenticated)​

MethodPathPurpose
GET/api/v1/brain/formsList your forms
POST/api/v1/brain/formsCreate (name + databaseId or createDatabase.name; optional config, syncFields)
GET/api/v1/brain/forms/:idEditor payload (form + fields)
PATCH/api/v1/brain/forms/:idUpdate name/config
DELETE/api/v1/brain/forms/:idDelete
POST/api/v1/brain/forms/:id/publish{ "published": true | false } — assigns a publicSlug when publishing
POST/api/v1/brain/forms/:id/submit{ "values": { … } } — owner/session submit (same pipeline as MCP brain_form_submit)

POST …/submit and the public submit both create a database record and fire webhook brain_form_submitted for the form owner. See Outgoing webhooks.

# Session cookie from a logged-in browser, or replay it in curl
curl -X POST https://arcos.corgicy.com/api/v1/brain/forms \
-H "Content-Type: application/json" \
-b "token=YOUR_SESSION" \
-d '{"name":"Intake","createDatabase":{"name":"Intake responses"}}'
POST /api/v1/brain/forms/{id}/submit
Content-Type: application/json

{ "values": { "message": "Hello" } }

201 with { form, database, record, quiz?, thankYouMessage, … }. Errors: NOT_FOUND 404, VALIDATION 400, ALREADY_SUBMITTED 409, CLOSED 410.

Forms (public, no login required)​

Published UI: https://arcos.corgicy.com/brain/f/{publicSlug}

MethodPathPurpose
GET/api/v1/brain/public/forms/:publicSlugLoad questions (optional session). Password: header X-Brain-Access-Password or query accessPassword
POST/api/v1/brain/public/forms/:publicSlug/submit{ "values": { … }, "accessPassword"?, "respondentKey"? }

Submit sets respondent cookies when the form is one-response-per-person. Rate-limited per slug.

curl -X POST https://arcos.corgicy.com/api/v1/brain/public/forms/support-intake/submit \
-H "Content-Type: application/json" \
-d '{"values":{"message":"Need help with billing"}}'

Other Brain session routes (summary)​

Notes, folders, search, tags, links, graph, views, canvases, attachments, properties, databases (fields/records/views/CSV), templates, filing rules, sync, export/import, stats, embeds. See ENDPOINTS.md — Brain.