Amba

Projects

Create, list, update, delete, and reprovision Amba projects; manage API keys.

Projects are the top-level unit of tenancy. Creating one provisions a fresh Neon database (via the PROJECT_PROVISIONING Temporal workflow), stores its connection string in GCP Secret Manager, runs tenant migrations, seeds system segments, and mints an initial client + server API key pair.

Source: apps/api/src/routes/admin/projects.ts.

Endpoints

MethodPathDescription
POST/admin/projectsCreate a project (+ initial API keys, kicks off provisioning).
GET/admin/projectsList all projects owned by the developer.
GET/admin/projects/:projectIdProject details + API keys + integrations.
PATCH/admin/projects/:projectIdUpdate name, bundle id, platform, environment.
DELETE/admin/projects/:projectIdDelete project row (does not tear down the Neon DB).
POST/admin/projects/:projectId/api-keysMint an additional API key.
DELETE/admin/projects/:projectId/api-keys/:keyIdDeactivate an API key.
POST/admin/projects/:projectId/reprovisionRe-drive a failed or stalled provision.
GET/admin/projects/:projectId/provisioning-statusPoll the Neon mapping + Temporal workflow status.

POST /admin/projects

Create a project.

Request

POST /admin/projects
Authorization: Bearer <developer-access-token>
Content-Type: application/json
FieldTypeRequiredDescription
namestringyesDisplay name.
bundle_idstringnoMobile app bundle id (used as default Apple audience).
platform"ios" | "android" | "all"noDefaults to "all".

Response 201

{
  "data": {
    "project": {
      "id": "…",
      "name": "…",
      "developer_id": "…",
      "platform": "all",
      "created_at": "…"
    },
    "api_keys": {
      "client": { "key": "amb_client_ck_…", "prefix": "amb_client_ck_xxxxxxxx", "type": "client" },
      "server": { "key": "amb_server_sk_…", "prefix": "amb_server_sk_xxxxxxxx", "type": "server" }
    },
    "provisioning_status": "provisioning"
  }
}

provisioning_status is "provisioning" if the Temporal workflow started, "failed" if it didn't (the project still exists — call POST /admin/projects/:projectId/reprovision to retry).

Errors

  • 500 CREATE_FAILED — control plane write failed.

Try it:

POST/admin/projects
developer auth
curl -X POST 'https://api.amba.dev/admin/projects'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X POST '${BASE_URL}/admin/projects' \
  -H 'Authorization: Bearer ${DEV_TOKEN}' \
  -H 'Content-Type: application/json' \
  -d '{}'

GET /admin/projects

List all projects owned by the authenticated developer, newest first.

Response 200

{ "data": [ { "id": "…", "name": "…", "created_at": "…" },  ] }

Try it:

GET/admin/projects
developer auth
curl -X GET 'https://api.amba.dev/admin/projects'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X GET '${BASE_URL}/admin/projects' \
  -H 'Authorization: Bearer ${DEV_TOKEN}'

GET /admin/projects/:projectId

Fetch a single project with API keys (hashes only) and integrations.

Response 200

{
  "data": {
    "id": "…",
    "name": "…",
    "developer_id": "…",
    "bundle_id": "…",
    "platform": "all",
    "api_keys": [
      {
        "id": "…",
        "key_prefix": "amb_client_ck_xxxxxxxx",
        "key_type": "client",
        "environment": "development",
        "is_active": true,
        "created_at": "…",
        "last_used_at": null
      }
    ],
    "integrations": [
      {
        "id": "…",
        "provider": "revenuecat",
        "is_active": true,
        "config": {
          /* provider-specific */
        }
      }
    ]
  }
}

Errors

  • 404 NOT_FOUND — project does not exist or is not owned by the developer.

Try it:

GET/admin/projects/%7B%7BprojectId%7D%7D
developer auth
curl -X GET 'https://api.amba.dev/admin/projects/%7B%7BprojectId%7D%7D'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X GET '${BASE_URL}/admin/projects/{projectId}' \
  -H 'Authorization: Bearer ${DEV_TOKEN}'

PATCH /admin/projects/:projectId

Update mutable fields. Unknown fields are silently ignored.

Request

FieldTypeNotes
namestring
bundle_idstring
platform"ios" | "android" | "all"
environmentstringDeveloper-chosen label.

An empty body is a no-op that returns the current row.

Response 200

{ "data": { "id": "…", "name": "…", "updated_at": "…" } }

Errors

  • 404 NOT_FOUND.
  • 500 UPDATE_FAILED.

Try it:

PATCH/admin/projects/%7B%7BprojectId%7D%7D
developer auth
curl -X PATCH 'https://api.amba.dev/admin/projects/%7B%7BprojectId%7D%7D'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X PATCH '${BASE_URL}/admin/projects/{projectId}' \
  -H 'Authorization: Bearer ${DEV_TOKEN}' \
  -H 'Content-Type: application/json' \
  -d '{}'

DELETE /admin/projects/:projectId

Delete the control-plane project row. Does NOT delete the Neon DB — operate on the Neon console / Terraform to reclaim storage.

Response 200

{ "data": { "deleted": true } }

Errors

  • 404 NOT_FOUND.
  • 500 DELETE_FAILED.

Try it:

DELETE/admin/projects/%7B%7BprojectId%7D%7D
developer auth
curl -X DELETE 'https://api.amba.dev/admin/projects/%7B%7BprojectId%7D%7D'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X DELETE '${BASE_URL}/admin/projects/{projectId}' \
  -H 'Authorization: Bearer ${DEV_TOKEN}'

POST /admin/projects/:projectId/api-keys

Mint an additional API key.

Request

FieldTypeRequiredAllowed values
key_typestringyes"client", "server"
environmentstringyes"development", "production"

Response 201

{
  "data": {
    "key": "amb_client_ck_…",
    "prefix": "amb_client_ck_xxxxxxxx",
    "type": "client",
    "environment": "production"
  }
}

The full key is returned ONCE and never again — the control plane stores only a bcrypt-like hash.

Errors

  • 400 INVALID_INPUT — bad key_type or environment.
  • 404 NOT_FOUND.
  • 500 CREATE_FAILED.

Try it:

POST/admin/projects/%7B%7BprojectId%7D%7D/api-keys
developer auth
curl -X POST 'https://api.amba.dev/admin/projects/%7B%7BprojectId%7D%7D/api-keys'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X POST '${BASE_URL}/admin/projects/{projectId}/api-keys' \
  -H 'Authorization: Bearer ${DEV_TOKEN}' \
  -H 'Content-Type: application/json' \
  -d '{}'

DELETE /admin/projects/:projectId/api-keys/:keyId

Deactivate (is_active = false) an API key. Subsequent requests with that key fail 401 UNAUTHORIZED.

Response 200

{ "data": { "revoked": true } }

Errors

  • 404 NOT_FOUND.
  • 500 REVOKE_FAILED.

Try it:

DELETE/admin/projects/%7B%7BprojectId%7D%7D/api-keys/%7B%7BkeyId%7D%7D
developer auth
curl -X DELETE 'https://api.amba.dev/admin/projects/%7B%7BprojectId%7D%7D/api-keys/%7B%7BkeyId%7D%7D'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X DELETE '${BASE_URL}/admin/projects/{projectId}/api-keys/{keyId}' \
  -H 'Authorization: Bearer ${DEV_TOKEN}'

POST /admin/projects/:projectId/reprovision

Re-drive a failed or stalled PROJECT_PROVISIONING workflow. Idempotent — duplicate POSTs collapse onto the same workflow id. Serialized under a DB row lock so two concurrent calls can't both start workflows.

Response 200

{
  "data": {
    "projectId": "…",
    "workflow_id": "reprovision-<project>-<ms>",
    "status": "provisioning"
  }
}

If a prior workflow is already running at the same millisecond, the response additionally includes "collapsed": true.

Errors

  • 404 NOT_FOUND — project not owned by the developer.
  • 409 ALREADY_ACTIVE — project is already active; reprovision is not permitted.
  • 409 ARCHIVED — archived projects cannot be reprovisioned.
  • 409 PROVISIONING_IN_PROGRESS — a workflow is already running. error.details.workflow_id + .status describe it.
  • 502 REPROVISION_FAILED — Temporal refused to start the workflow; the DB UPDATE was rolled back.
  • 500 REPROVISION_FAILED — unexpected error.

Try it:

POST/admin/projects/%7B%7BprojectId%7D%7D/reprovision
developer auth
curl -X POST 'https://api.amba.dev/admin/projects/%7B%7BprojectId%7D%7D/reprovision'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X POST '${BASE_URL}/admin/projects/{projectId}/reprovision' \
  -H 'Authorization: Bearer ${DEV_TOKEN}' \
  -H 'Content-Type: application/json' \
  -d '{}'

GET /admin/projects/:projectId/provisioning-status

Poll the current Neon mapping row and describe the latest provisioning workflow. Safe to poll every few seconds.

Response 200

{
  "data": {
    "projectId": "…",
    "mapping": {
      "project_id": "…",
      "neon_project_id": "…",
      "neon_endpoint_id": "…",
      "neon_branch_id": "…",
      "secret_manager_secret_name": "…",
      "pg_version": "16",
      "region": "aws-us-east-1",
      "status": "active",
      "provisioned_at": "…",
      "last_migrated_version": 15,
      "current_workflow_id": "project-provisioning-…",
      "created_at": "…",
      "updated_at": "…"
    },
    "workflow": {
      "workflow_id": "project-provisioning-…",
      "run_id": "…",
      "status": "COMPLETED",
      "start_time": "…",
      "close_time": "…"
    }
  }
}

mapping is null if no row exists yet. workflow is null if the mapping's current_workflow_id doesn't resolve to a Temporal execution (e.g. create-time failure where Temporal was never reached).

Errors

  • 404 NOT_FOUND.
  • 500 PROVISIONING_STATUS_FAILED.

Try it:

GET/admin/projects/%7B%7BprojectId%7D%7D/provisioning-status
developer auth
curl -X GET 'https://api.amba.dev/admin/projects/%7B%7BprojectId%7D%7D/provisioning-status'
Loading auth… Configure auth in the settings drawer (top-right) to run this request.

Curl:

curl -X GET '${BASE_URL}/admin/projects/{projectId}/provisioning-status' \
  -H 'Authorization: Bearer ${DEV_TOKEN}'