Projects
A project is one of your end-customers. Content, influencers, social accounts, and metrics all scope to it.
A project represents a single end-customer's product. Their app, their website, their brand, their social presence — one project. You create projects on behalf of your customers, and everything downstream (influencers, generated content, connected social accounts, SDK telemetry, ads metrics) nests inside that project.
If you've worked with Stripe, think of a project the way you'd think of a Connect account: it's the tenant boundary for a single customer, inside your org.
Creating a project
You create one with POST /v1/projects. name and timezone are required:
{
"name": "Acme Fitness",
"customerExternalId": "acme-prod-987",
"timezone": "America/Los_Angeles"
}name— human label shown in your customer's surface and in the Layers admin.customerExternalId— your stable handle for this customer. Stored as free text onmetadata.partner.customerExternalId; the server does not enforce uniqueness. Make it unique within your org on your side soGET /v1/projects?customerExternalId=...is unambiguous.timezone— IANA timezone. Feeds scheduling and per-day metrics rollups.
Set an Idempotency-Key header to make retries safe — same key + same body replays the original response; different body → 409 IDEMPOTENCY_CONFLICT. See idempotency.
Creating a project is synchronous (201 Created with the full project row). Ingesting the brand (a GitHub repo, a website, an App Store listing) is the async step that runs behind a job. A fresh project has no brand context until you run one of the ingest endpoints.
What lives inside a project
| Resource | Purpose |
|---|---|
| Brand context | App name, description, tagline, voice, ICP, keywords, primary language. Populated by POST /v1/projects/:id/ingest/github, /ingest/website, or /ingest/appstore. |
| SDK apps | One per platform (iOS, Android, web). Each has its own ingest endpoint and CAPI config. |
| Influencers | Brand-ambassador personas that drive UGC generation. |
| Content items | Generated videos, slideshows, UGC remixes. |
| Social accounts | Connected (user-owned OAuth) and leased (Layers-owned) accounts available for publishing. |
| Scheduled posts | The queue of outgoing posts against those accounts. |
| Approval policy | requires_approval + first_n_posts_blocked. Lives on the project row. |
| Metrics | Organic performance, ads performance, top-performers, conversions. |
Customer scoping
Every project carries customerExternalId — a free-text field you should make unique within your org (the server does not enforce uniqueness). It lets you unambiguously answer "which of my customers does this project belong to" without trusting the caller.
Lookup. GET /v1/projects?customerExternalId=acme-prod-987 returns the project.
Assertion. Read the project first via GET /v1/projects/:projectId and assert the returned customerExternalId matches what your code expects before issuing follow-up calls. Every project-scoped route (/v1/projects/:projectId/...) is server-side restricted to projects inside your org — attempts to touch other orgs' projects return 404 NOT_FOUND, so a stolen or confused projectId can't leak data, but only your assertion protects against confused-deputy bugs in your own code.
Mutability and archival
PATCH /v1/projects/:idupdates user-editable fields:name,timezone,primaryLanguage, brand fields,metadata. System fields likeid,organizationId,createdAtare immutable.DELETE /v1/projects/:idis a soft archive. It flipsstatustoarchived, cancels any queued scheduled posts, and stops the project from being surfaced in list endpoints. The underlying data stays; if you need a hard purge, that's an ops request.- Archived projects can't receive new content generations, new social connections, or new ingests. You can still read historical metrics.