@playaos/api-client 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # @playaos/api-client
2
+
3
+ Typed JavaScript/TypeScript client for the [PlayaOS](https://playaos.app) REST API.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @playaos/api-client
9
+ ```
10
+
11
+ ## Quick start
12
+
13
+ ```typescript
14
+ import { createClient } from "@playaos/api-client";
15
+
16
+ const client = createClient({
17
+ baseUrl: "https://your-camp.playaos.app",
18
+ apiKey: "pk_live_...",
19
+ });
20
+
21
+ // List members
22
+ const members = await client.members.list();
23
+
24
+ // Filter by role
25
+ const admins = await client.members.list({ role: "admin" });
26
+
27
+ // Get a single member
28
+ const member = await client.members.get("uuid-here");
29
+
30
+ // List applications
31
+ const pending = await client.applications.list({ status: "pending", year: 2025 });
32
+
33
+ // List dues status
34
+ const dues = await client.dues.list({ year: 2025 });
35
+
36
+ // List shifts
37
+ const shifts = await client.shifts.list({ publishedOnly: true, year: 2025 });
38
+
39
+ // Get org config
40
+ const org = await client.org.get();
41
+ ```
42
+
43
+ ## Authentication
44
+
45
+ Generate an API key in **Admin → Settings → Developer**. Keys use the format `pk_live_*` and are scoped to specific resources.
46
+
47
+ Required scopes per endpoint:
48
+
49
+ | Endpoint | Scope |
50
+ |----------|-------|
51
+ | `members.list` / `members.get` | `members:read` |
52
+ | `applications.list` | `applications:read` |
53
+ | `dues.list` | `dues:read` |
54
+ | `shifts.list` | `shifts:read` |
55
+ | `org.get` | `org:read` |
56
+
57
+ ## Error handling
58
+
59
+ ```typescript
60
+ import { ApiClientError } from "@playaos/api-client";
61
+
62
+ try {
63
+ const member = await client.members.get("nonexistent-id");
64
+ } catch (err) {
65
+ if (err instanceof ApiClientError) {
66
+ console.error(err.status, err.code, err.message);
67
+ // 404, "NOT_FOUND", "Member not found"
68
+ }
69
+ }
70
+ ```
71
+
72
+ ## MCP server
73
+
74
+ For AI agent access, use the companion [@playaos/mcp](https://www.npmjs.com/package/@playaos/mcp) package.
@@ -0,0 +1,97 @@
1
+ import { z } from 'zod';
2
+
3
+ declare const step0Schema: z.ZodObject<{
4
+ acknowledged_expectations: z.ZodLiteral<true>;
5
+ }, z.core.$strip>;
6
+ declare const step1Schema: z.ZodObject<{
7
+ first_name: z.ZodString;
8
+ last_name: z.ZodString;
9
+ email: z.ZodString;
10
+ phone: z.ZodString;
11
+ referral_code: z.ZodOptional<z.ZodString>;
12
+ socials: z.ZodOptional<z.ZodString>;
13
+ birthday: z.ZodString;
14
+ hometown: z.ZodString;
15
+ }, z.core.$strip>;
16
+ declare const step2Schema: z.ZodObject<{
17
+ how_heard: z.ZodString;
18
+ burning_man_before: z.ZodEnum<{
19
+ yes: "yes";
20
+ no: "no";
21
+ }>;
22
+ burning_man_years: z.ZodOptional<z.ZodString>;
23
+ }, z.core.$strip>;
24
+ declare const step3Schema: z.ZodObject<{
25
+ shelter_type: z.ZodEnum<{
26
+ joining_rv: "joining_rv";
27
+ bringing_rv: "bringing_rv";
28
+ shiftpod_tent: "shiftpod_tent";
29
+ car: "car";
30
+ truck: "truck";
31
+ van: "van";
32
+ unsure: "unsure";
33
+ }>;
34
+ shelter_mates: z.ZodOptional<z.ZodString>;
35
+ ticket_status: z.ZodEnum<{
36
+ unsure: "unsure";
37
+ looking: "looking";
38
+ have_ticket: "have_ticket";
39
+ }>;
40
+ }, z.core.$strip>;
41
+ declare const step4Schema: z.ZodObject<{
42
+ build_available: z.ZodBoolean;
43
+ build_skills: z.ZodOptional<z.ZodString>;
44
+ strike_available: z.ZodBoolean;
45
+ strike_help: z.ZodOptional<z.ZodString>;
46
+ }, z.core.$strip>;
47
+ declare const step5Schema: z.ZodObject<{
48
+ about_yourself: z.ZodString;
49
+ agrees_to_principles: z.ZodLiteral<true>;
50
+ }, z.core.$strip>;
51
+ declare const applicationSchema: z.ZodObject<{
52
+ acknowledged_expectations: z.ZodLiteral<true>;
53
+ first_name: z.ZodString;
54
+ last_name: z.ZodString;
55
+ email: z.ZodString;
56
+ phone: z.ZodString;
57
+ referral_code: z.ZodOptional<z.ZodString>;
58
+ socials: z.ZodOptional<z.ZodString>;
59
+ birthday: z.ZodString;
60
+ hometown: z.ZodString;
61
+ how_heard: z.ZodString;
62
+ burning_man_before: z.ZodEnum<{
63
+ yes: "yes";
64
+ no: "no";
65
+ }>;
66
+ burning_man_years: z.ZodOptional<z.ZodString>;
67
+ shelter_type: z.ZodEnum<{
68
+ joining_rv: "joining_rv";
69
+ bringing_rv: "bringing_rv";
70
+ shiftpod_tent: "shiftpod_tent";
71
+ car: "car";
72
+ truck: "truck";
73
+ van: "van";
74
+ unsure: "unsure";
75
+ }>;
76
+ ticket_status: z.ZodEnum<{
77
+ unsure: "unsure";
78
+ looking: "looking";
79
+ have_ticket: "have_ticket";
80
+ }>;
81
+ shelter_mates: z.ZodOptional<z.ZodString>;
82
+ build_available: z.ZodBoolean;
83
+ build_skills: z.ZodOptional<z.ZodString>;
84
+ strike_available: z.ZodBoolean;
85
+ strike_help: z.ZodOptional<z.ZodString>;
86
+ about_yourself: z.ZodString;
87
+ agrees_to_principles: z.ZodLiteral<true>;
88
+ }, z.core.$strip>;
89
+ type ApplicationFormData = z.infer<typeof applicationSchema>;
90
+ type Step0Data = z.infer<typeof step0Schema>;
91
+ type Step1Data = z.infer<typeof step1Schema>;
92
+ type Step2Data = z.infer<typeof step2Schema>;
93
+ type Step3Data = z.infer<typeof step3Schema>;
94
+ type Step4Data = z.infer<typeof step4Schema>;
95
+ type Step5Data = z.infer<typeof step5Schema>;
96
+
97
+ export { type ApplicationFormData, type Step0Data, type Step1Data, type Step2Data, type Step3Data, type Step4Data, type Step5Data, applicationSchema, step0Schema, step1Schema, step2Schema, step3Schema, step4Schema, step5Schema };
@@ -0,0 +1,63 @@
1
+ // src/applications-schema.ts
2
+ import { z } from "zod";
3
+ var step0Schema = z.object({
4
+ acknowledged_expectations: z.literal(true, {
5
+ error: "Please acknowledge the expectations to continue"
6
+ })
7
+ });
8
+ var step1Schema = z.object({
9
+ first_name: z.string().min(1, "First name is required"),
10
+ last_name: z.string().min(1, "Last name is required"),
11
+ email: z.string().email("Invalid email address"),
12
+ phone: z.string().min(10, "Phone number is required"),
13
+ referral_code: z.string().optional(),
14
+ socials: z.string().optional(),
15
+ birthday: z.string().min(1, "Birthday is required"),
16
+ hometown: z.string().min(1, "Location is required")
17
+ });
18
+ var step2Schema = z.object({
19
+ how_heard: z.string().min(1, "Please tell us how you heard about us"),
20
+ burning_man_before: z.enum(["yes", "no"], { error: "Please select an option" }),
21
+ burning_man_years: z.string().optional()
22
+ });
23
+ var step3Schema = z.object({
24
+ shelter_type: z.enum(["joining_rv", "bringing_rv", "shiftpod_tent", "car", "truck", "van", "unsure"], {
25
+ error: "Please select a shelter type"
26
+ }),
27
+ shelter_mates: z.string().optional(),
28
+ ticket_status: z.enum(["looking", "have_ticket", "unsure"], { error: "Please select your ticket status" })
29
+ });
30
+ var step4Schema = z.object({
31
+ build_available: z.boolean(),
32
+ build_skills: z.string().optional(),
33
+ strike_available: z.boolean(),
34
+ strike_help: z.string().optional()
35
+ }).refine((data) => !data.build_available || data.build_skills, {
36
+ message: "Please tell us about your build skills",
37
+ path: ["build_skills"]
38
+ }).refine((data) => !data.strike_available || data.strike_help, {
39
+ message: "Please tell us how you can help with strike",
40
+ path: ["strike_help"]
41
+ });
42
+ var step5Schema = z.object({
43
+ about_yourself: z.string().min(50, "Please write at least 50 characters about yourself"),
44
+ agrees_to_principles: z.literal(true, { error: "You must agree to the 10 Principles" })
45
+ });
46
+ var applicationSchema = step0Schema.merge(step1Schema).merge(step2Schema).merge(step3Schema.omit({ shelter_mates: true })).extend({ shelter_mates: z.string().optional() }).merge(
47
+ z.object({
48
+ build_available: z.boolean(),
49
+ build_skills: z.string().optional(),
50
+ strike_available: z.boolean(),
51
+ strike_help: z.string().optional()
52
+ })
53
+ ).merge(step5Schema);
54
+ export {
55
+ applicationSchema,
56
+ step0Schema,
57
+ step1Schema,
58
+ step2Schema,
59
+ step3Schema,
60
+ step4Schema,
61
+ step5Schema
62
+ };
63
+ //# sourceMappingURL=applications-schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/applications-schema.ts"],"sourcesContent":["import { z } from \"zod\";\n\n// Single source of truth for the apply-wizard form shape.\n// The portal apply flow and the @playaos/react <ApplicationForm> SDK both import\n// from here so the validation contract can never drift between them (PLA-483).\n// `ApplicationCreatePayload` in ./types.ts is the zod-free wire mirror; a type\n// equivalence test (applications-schema.test.ts) keeps the two locked together.\n\n// Per-step validation schemas\nexport const step0Schema = z.object({\n acknowledged_expectations: z.literal(true, {\n error: \"Please acknowledge the expectations to continue\",\n }),\n});\n\nexport const step1Schema = z.object({\n first_name: z.string().min(1, \"First name is required\"),\n last_name: z.string().min(1, \"Last name is required\"),\n email: z.string().email(\"Invalid email address\"),\n phone: z.string().min(10, \"Phone number is required\"),\n referral_code: z.string().optional(),\n socials: z.string().optional(),\n birthday: z.string().min(1, \"Birthday is required\"),\n hometown: z.string().min(1, \"Location is required\"),\n});\n\nexport const step2Schema = z.object({\n how_heard: z.string().min(1, \"Please tell us how you heard about us\"),\n burning_man_before: z.enum([\"yes\", \"no\"], { error: \"Please select an option\" }),\n burning_man_years: z.string().optional(),\n});\n\nexport const step3Schema = z.object({\n shelter_type: z.enum([\"joining_rv\", \"bringing_rv\", \"shiftpod_tent\", \"car\", \"truck\", \"van\", \"unsure\"], {\n error: \"Please select a shelter type\",\n }),\n shelter_mates: z.string().optional(),\n ticket_status: z.enum([\"looking\", \"have_ticket\", \"unsure\"], { error: \"Please select your ticket status\" }),\n});\n\nexport const step4Schema = z\n .object({\n build_available: z.boolean(),\n build_skills: z.string().optional(),\n strike_available: z.boolean(),\n strike_help: z.string().optional(),\n })\n .refine((data) => !data.build_available || data.build_skills, {\n message: \"Please tell us about your build skills\",\n path: [\"build_skills\"],\n })\n .refine((data) => !data.strike_available || data.strike_help, {\n message: \"Please tell us how you can help with strike\",\n path: [\"strike_help\"],\n });\n\nexport const step5Schema = z.object({\n about_yourself: z.string().min(50, \"Please write at least 50 characters about yourself\"),\n agrees_to_principles: z.literal(true, { error: \"You must agree to the 10 Principles\" }),\n});\n\n// Combined schema for full submission\nexport const applicationSchema = step0Schema\n .merge(step1Schema)\n .merge(step2Schema)\n .merge(step3Schema.omit({ shelter_mates: true }))\n .extend({ shelter_mates: z.string().optional() })\n .merge(\n z.object({\n build_available: z.boolean(),\n build_skills: z.string().optional(),\n strike_available: z.boolean(),\n strike_help: z.string().optional(),\n }),\n )\n .merge(step5Schema);\n\nexport type ApplicationFormData = z.infer<typeof applicationSchema>;\nexport type Step0Data = z.infer<typeof step0Schema>;\nexport type Step1Data = z.infer<typeof step1Schema>;\nexport type Step2Data = z.infer<typeof step2Schema>;\nexport type Step3Data = z.infer<typeof step3Schema>;\nexport type Step4Data = z.infer<typeof step4Schema>;\nexport type Step5Data = z.infer<typeof step5Schema>;\n"],"mappings":";AAAA,SAAS,SAAS;AASX,IAAM,cAAc,EAAE,OAAO;AAAA,EAClC,2BAA2B,EAAE,QAAQ,MAAM;AAAA,IACzC,OAAO;AAAA,EACT,CAAC;AACH,CAAC;AAEM,IAAM,cAAc,EAAE,OAAO;AAAA,EAClC,YAAY,EAAE,OAAO,EAAE,IAAI,GAAG,wBAAwB;AAAA,EACtD,WAAW,EAAE,OAAO,EAAE,IAAI,GAAG,uBAAuB;AAAA,EACpD,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB;AAAA,EAC/C,OAAO,EAAE,OAAO,EAAE,IAAI,IAAI,0BAA0B;AAAA,EACpD,eAAe,EAAE,OAAO,EAAE,SAAS;AAAA,EACnC,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,UAAU,EAAE,OAAO,EAAE,IAAI,GAAG,sBAAsB;AAAA,EAClD,UAAU,EAAE,OAAO,EAAE,IAAI,GAAG,sBAAsB;AACpD,CAAC;AAEM,IAAM,cAAc,EAAE,OAAO;AAAA,EAClC,WAAW,EAAE,OAAO,EAAE,IAAI,GAAG,uCAAuC;AAAA,EACpE,oBAAoB,EAAE,KAAK,CAAC,OAAO,IAAI,GAAG,EAAE,OAAO,0BAA0B,CAAC;AAAA,EAC9E,mBAAmB,EAAE,OAAO,EAAE,SAAS;AACzC,CAAC;AAEM,IAAM,cAAc,EAAE,OAAO;AAAA,EAClC,cAAc,EAAE,KAAK,CAAC,cAAc,eAAe,iBAAiB,OAAO,SAAS,OAAO,QAAQ,GAAG;AAAA,IACpG,OAAO;AAAA,EACT,CAAC;AAAA,EACD,eAAe,EAAE,OAAO,EAAE,SAAS;AAAA,EACnC,eAAe,EAAE,KAAK,CAAC,WAAW,eAAe,QAAQ,GAAG,EAAE,OAAO,mCAAmC,CAAC;AAC3G,CAAC;AAEM,IAAM,cAAc,EACxB,OAAO;AAAA,EACN,iBAAiB,EAAE,QAAQ;AAAA,EAC3B,cAAc,EAAE,OAAO,EAAE,SAAS;AAAA,EAClC,kBAAkB,EAAE,QAAQ;AAAA,EAC5B,aAAa,EAAE,OAAO,EAAE,SAAS;AACnC,CAAC,EACA,OAAO,CAAC,SAAS,CAAC,KAAK,mBAAmB,KAAK,cAAc;AAAA,EAC5D,SAAS;AAAA,EACT,MAAM,CAAC,cAAc;AACvB,CAAC,EACA,OAAO,CAAC,SAAS,CAAC,KAAK,oBAAoB,KAAK,aAAa;AAAA,EAC5D,SAAS;AAAA,EACT,MAAM,CAAC,aAAa;AACtB,CAAC;AAEI,IAAM,cAAc,EAAE,OAAO;AAAA,EAClC,gBAAgB,EAAE,OAAO,EAAE,IAAI,IAAI,oDAAoD;AAAA,EACvF,sBAAsB,EAAE,QAAQ,MAAM,EAAE,OAAO,sCAAsC,CAAC;AACxF,CAAC;AAGM,IAAM,oBAAoB,YAC9B,MAAM,WAAW,EACjB,MAAM,WAAW,EACjB,MAAM,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC,CAAC,EAC/C,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,EAC/C;AAAA,EACC,EAAE,OAAO;AAAA,IACP,iBAAiB,EAAE,QAAQ;AAAA,IAC3B,cAAc,EAAE,OAAO,EAAE,SAAS;AAAA,IAClC,kBAAkB,EAAE,QAAQ;AAAA,IAC5B,aAAa,EAAE,OAAO,EAAE,SAAS;AAAA,EACnC,CAAC;AACH,EACC,MAAM,WAAW;","names":[]}
@@ -0,0 +1,246 @@
1
+ declare class ApiClientError extends Error {
2
+ readonly status: number;
3
+ readonly code: string;
4
+ constructor(status: number, code: string, message: string);
5
+ }
6
+
7
+ interface Member {
8
+ id: string;
9
+ firstName: string;
10
+ lastName: string;
11
+ name: string | null;
12
+ email: string;
13
+ role: string | null;
14
+ status: string | null;
15
+ orgId: string;
16
+ createdAt: string | null;
17
+ }
18
+ interface Application {
19
+ id: string;
20
+ userId: string;
21
+ orgId: string;
22
+ status: string | null;
23
+ year: number;
24
+ createdAt: string | null;
25
+ updatedAt: string | null;
26
+ }
27
+ interface DuesStatus {
28
+ userId: string | null;
29
+ year: number | null;
30
+ paymentStatus: string | null;
31
+ amountOwed: string | null;
32
+ amountPaid: string | null;
33
+ balance: string | null;
34
+ }
35
+ interface ShiftSignup {
36
+ id: string;
37
+ userId: string;
38
+ status: string | null;
39
+ role: string | null;
40
+ }
41
+ interface ShiftType {
42
+ id: string;
43
+ name: string;
44
+ description: string | null;
45
+ }
46
+ interface Shift {
47
+ id: string;
48
+ orgId: string;
49
+ shiftDate: string;
50
+ year: number;
51
+ startTime: string | null;
52
+ endTime: string | null;
53
+ maxSlots: number | null;
54
+ isPublished: boolean | null;
55
+ shiftType: ShiftType | null;
56
+ shiftSignups: ShiftSignup[];
57
+ }
58
+ interface OrgConfig {
59
+ id: string;
60
+ name: string;
61
+ slug: string;
62
+ preset: string | null;
63
+ customDomain: string | null;
64
+ settings: Record<string, unknown> | null;
65
+ features: Record<string, unknown> | null;
66
+ plan: string | null;
67
+ logoUrl: string | null;
68
+ }
69
+ type ApplicationStatus = "draft" | "pending" | "invite_sent" | "interview_scheduled" | "interviewed" | "approved" | "waitlisted" | "rejected";
70
+ type MemberRole = "member" | "admin" | "applicant" | "super_admin";
71
+ interface PaymentPageResponse {
72
+ provider: "givebutter" | "crowded";
73
+ url: string;
74
+ modalSupported: boolean;
75
+ }
76
+ interface ApplicationCreatePayload {
77
+ acknowledged_expectations: true;
78
+ first_name: string;
79
+ last_name: string;
80
+ email: string;
81
+ phone: string;
82
+ referral_code?: string;
83
+ socials?: string;
84
+ birthday: string;
85
+ hometown: string;
86
+ how_heard: string;
87
+ burning_man_before: "yes" | "no";
88
+ burning_man_years?: string;
89
+ shelter_type: "joining_rv" | "bringing_rv" | "shiftpod_tent" | "car" | "truck" | "van" | "unsure";
90
+ shelter_mates?: string;
91
+ ticket_status: "looking" | "have_ticket" | "unsure";
92
+ build_available: boolean;
93
+ build_skills?: string;
94
+ strike_available: boolean;
95
+ strike_help?: string;
96
+ about_yourself: string;
97
+ agrees_to_principles: true;
98
+ }
99
+ interface ApplicationCreateResponse {
100
+ applicationId: string;
101
+ status: string;
102
+ }
103
+ interface ApplicationTransitionPayload {
104
+ to: ApplicationStatus;
105
+ rejectionReason?: string;
106
+ }
107
+ interface ApplicationTransitionResponse {
108
+ applicationId: string;
109
+ status: ApplicationStatus;
110
+ }
111
+ interface MemberCreatePayload {
112
+ firstName: string;
113
+ lastName: string;
114
+ email: string;
115
+ phone?: string | null;
116
+ role?: "member" | "admin" | "super_admin";
117
+ status?: string;
118
+ }
119
+ interface MemberCreateResponse {
120
+ profileId: string;
121
+ }
122
+ interface MemberUpdatePayload {
123
+ firstName?: string;
124
+ lastName?: string;
125
+ phone?: string | null;
126
+ }
127
+ interface MemberUpdateResponse {
128
+ profileId: string;
129
+ }
130
+ interface MemberDeactivateResponse {
131
+ profileId: string;
132
+ status: "inactive";
133
+ }
134
+
135
+ interface ClientOptions {
136
+ /** Base URL of the PlayaOS portal, e.g. https://your-camp.playaos.app */
137
+ baseUrl: string;
138
+ /** API key in the format pk_live_* */
139
+ apiKey: string;
140
+ }
141
+ declare class PlayaOSClient {
142
+ private readonly baseUrl;
143
+ private readonly apiKey;
144
+ constructor(opts: ClientOptions);
145
+ readonly members: {
146
+ list: (params?: {
147
+ role?: MemberRole;
148
+ status?: string;
149
+ }, opts?: {
150
+ signal?: AbortSignal;
151
+ }) => Promise<Member[]>;
152
+ get: (id: string, opts?: {
153
+ signal?: AbortSignal;
154
+ }) => Promise<Member>;
155
+ };
156
+ readonly applications: {
157
+ list: (params?: {
158
+ status?: ApplicationStatus;
159
+ year?: number;
160
+ }, opts?: {
161
+ signal?: AbortSignal;
162
+ }) => Promise<Application[]>;
163
+ create: (payload: ApplicationCreatePayload, opts?: {
164
+ accessToken?: string;
165
+ signal?: AbortSignal;
166
+ }) => Promise<ApplicationCreateResponse>;
167
+ };
168
+ readonly dues: {
169
+ list: (params?: {
170
+ userId?: string;
171
+ year?: number;
172
+ }, opts?: {
173
+ signal?: AbortSignal;
174
+ }) => Promise<DuesStatus[]>;
175
+ };
176
+ readonly shifts: {
177
+ list: (params?: {
178
+ year?: number;
179
+ fromDate?: string;
180
+ toDate?: string;
181
+ publishedOnly?: boolean;
182
+ }, opts?: {
183
+ signal?: AbortSignal;
184
+ }) => Promise<Shift[]>;
185
+ };
186
+ readonly org: {
187
+ get: (opts?: {
188
+ signal?: AbortSignal;
189
+ }) => Promise<OrgConfig>;
190
+ };
191
+ readonly payments: {
192
+ page: (memberId: string, duesCollectionId?: string, opts?: {
193
+ signal?: AbortSignal;
194
+ }) => Promise<PaymentPageResponse>;
195
+ };
196
+ readonly admin: {
197
+ applications: {
198
+ transition: (id: string, payload: ApplicationTransitionPayload, opts: {
199
+ accessToken: string;
200
+ signal?: AbortSignal;
201
+ }) => Promise<ApplicationTransitionResponse>;
202
+ };
203
+ members: {
204
+ create: (payload: MemberCreatePayload, opts: {
205
+ accessToken: string;
206
+ signal?: AbortSignal;
207
+ }) => Promise<MemberCreateResponse>;
208
+ update: (id: string, patch: MemberUpdatePayload, opts: {
209
+ accessToken: string;
210
+ signal?: AbortSignal;
211
+ }) => Promise<MemberUpdateResponse>;
212
+ deactivate: (id: string, opts: {
213
+ accessToken: string;
214
+ signal?: AbortSignal;
215
+ }) => Promise<MemberDeactivateResponse>;
216
+ };
217
+ };
218
+ }
219
+ /** Convenience factory — equivalent to `new PlayaOSClient(opts)`. */
220
+ declare function createClient(opts: ClientOptions): PlayaOSClient;
221
+
222
+ /**
223
+ * Server-side OAuth-code → ID-token exchange (PLA-573 T24).
224
+ *
225
+ * Used by camp sites' confidential-client backends to swap a one-time
226
+ * authorization code from the PlayaOS IdP for a short-lived ID token.
227
+ * Public/PKCE-only clients exchange directly from the browser via
228
+ * `usePlayaOSAuth` in `@playaos/react`; this helper exists for confidential
229
+ * clients that need to send the client_secret server-side.
230
+ */
231
+ interface ExchangeParams {
232
+ authBaseUrl?: string;
233
+ code: string;
234
+ codeVerifier: string;
235
+ clientId: string;
236
+ clientSecret: string;
237
+ redirectUri: string;
238
+ }
239
+ interface ExchangeResult {
240
+ idToken: string;
241
+ expiresAt: string;
242
+ refreshToken: string;
243
+ }
244
+ declare function exchangeCode(params: ExchangeParams): Promise<ExchangeResult>;
245
+
246
+ export { ApiClientError, type Application, type ApplicationCreatePayload, type ApplicationCreateResponse, type ApplicationStatus, type ApplicationTransitionPayload, type ApplicationTransitionResponse, type DuesStatus, type ExchangeParams, type ExchangeResult, type Member, type MemberCreatePayload, type MemberCreateResponse, type MemberDeactivateResponse, type MemberRole, type MemberUpdatePayload, type MemberUpdateResponse, type OrgConfig, type PaymentPageResponse, PlayaOSClient, type Shift, type ShiftSignup, type ShiftType, createClient, exchangeCode };
package/dist/index.js ADDED
@@ -0,0 +1,188 @@
1
+ // src/error.ts
2
+ var ApiClientError = class extends Error {
3
+ constructor(status, code, message) {
4
+ super(message);
5
+ this.status = status;
6
+ this.code = code;
7
+ this.name = "ApiClientError";
8
+ }
9
+ status;
10
+ code;
11
+ };
12
+ async function parseError(res) {
13
+ try {
14
+ const body = await res.json();
15
+ return new ApiClientError(res.status, body.code ?? "UNKNOWN", body.error ?? res.statusText);
16
+ } catch {
17
+ return new ApiClientError(res.status, "UNKNOWN", res.statusText);
18
+ }
19
+ }
20
+
21
+ // src/client.ts
22
+ function buildUrl(base, path, params) {
23
+ const url = new URL(`${base}/api/v1${path}`);
24
+ if (params) {
25
+ for (const [k, v] of Object.entries(params)) {
26
+ if (v !== void 0 && v !== false) url.searchParams.set(k, String(v));
27
+ }
28
+ }
29
+ return url.toString();
30
+ }
31
+ function buildEmbedUrl(base, path) {
32
+ return `${base}/api/embed/v1${path}`;
33
+ }
34
+ async function request(url, apiKey, signal) {
35
+ const res = await fetch(url, {
36
+ headers: { Authorization: `Bearer ${apiKey}` },
37
+ signal
38
+ });
39
+ if (!res.ok) throw await parseError(res);
40
+ return res.json();
41
+ }
42
+ async function post(url, apiKey, body, signal) {
43
+ const res = await fetch(url, {
44
+ method: "POST",
45
+ headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
46
+ body: JSON.stringify(body),
47
+ signal
48
+ });
49
+ if (!res.ok) throw await parseError(res);
50
+ return res.json();
51
+ }
52
+ async function postEmbed(url, campKey, body, opts) {
53
+ const headers = {
54
+ "X-Camp-Key": campKey,
55
+ "Content-Type": "application/json"
56
+ };
57
+ if (opts?.accessToken) headers.Authorization = `Bearer ${opts.accessToken}`;
58
+ const res = await fetch(url, {
59
+ method: "POST",
60
+ headers,
61
+ body: JSON.stringify(body),
62
+ signal: opts?.signal
63
+ });
64
+ if (!res.ok) throw await parseError(res);
65
+ return res.json();
66
+ }
67
+ async function adminEmbed(method, url, accessToken, body, signal) {
68
+ const res = await fetch(url, {
69
+ method,
70
+ headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" },
71
+ body: JSON.stringify(body),
72
+ signal
73
+ });
74
+ if (!res.ok) throw await parseError(res);
75
+ return res.json();
76
+ }
77
+ var PlayaOSClient = class {
78
+ baseUrl;
79
+ apiKey;
80
+ constructor(opts) {
81
+ this.baseUrl = opts.baseUrl.replace(/\/$/, "");
82
+ this.apiKey = opts.apiKey;
83
+ }
84
+ members = {
85
+ list: (params, opts) => request(buildUrl(this.baseUrl, "/members", params), this.apiKey, opts?.signal),
86
+ get: (id, opts) => request(buildUrl(this.baseUrl, `/members/${encodeURIComponent(id)}`), this.apiKey, opts?.signal)
87
+ };
88
+ applications = {
89
+ list: (params, opts) => request(buildUrl(this.baseUrl, "/applications", params), this.apiKey, opts?.signal),
90
+ // Anonymous external-camp submission against POST /api/embed/v1/applications
91
+ // (PLA-481). Uses the X-Camp-Key header rather than Bearer auth. Pass
92
+ // `accessToken` to additionally send a PlayaOS IdP JWT (PLA-573 path) so
93
+ // the server trusts the verified profile claim and skips the email-based
94
+ // bootstrap.
95
+ create: (payload, opts) => postEmbed(buildEmbedUrl(this.baseUrl, "/applications"), this.apiKey, payload, opts)
96
+ };
97
+ dues = {
98
+ list: (params, opts) => request(buildUrl(this.baseUrl, "/dues", params), this.apiKey, opts?.signal)
99
+ };
100
+ shifts = {
101
+ list: (params, opts) => request(buildUrl(this.baseUrl, "/shifts", params), this.apiKey, opts?.signal)
102
+ };
103
+ org = {
104
+ get: (opts) => request(buildUrl(this.baseUrl, "/org"), this.apiKey, opts?.signal)
105
+ };
106
+ payments = {
107
+ page: (memberId, duesCollectionId, opts) => post(buildUrl(this.baseUrl, "/payments/page"), this.apiKey, { memberId, duesCollectionId }, opts?.signal)
108
+ };
109
+ // Admin write APIs (PLA-489). Each method requires `accessToken` — a Supabase
110
+ // JWT for a camp admin/super_admin. The org boundary is enforced server-side
111
+ // from the JWT claims; these endpoints do not use the X-Camp-Key.
112
+ admin = {
113
+ applications: {
114
+ transition: (id, payload, opts) => adminEmbed(
115
+ "PATCH",
116
+ buildEmbedUrl(this.baseUrl, `/admin/applications/${encodeURIComponent(id)}`),
117
+ opts.accessToken,
118
+ payload,
119
+ opts.signal
120
+ )
121
+ },
122
+ members: {
123
+ create: (payload, opts) => adminEmbed("POST", buildEmbedUrl(this.baseUrl, "/admin/members"), opts.accessToken, payload, opts.signal),
124
+ update: (id, patch, opts) => adminEmbed(
125
+ "PATCH",
126
+ buildEmbedUrl(this.baseUrl, `/admin/members/${encodeURIComponent(id)}`),
127
+ opts.accessToken,
128
+ patch,
129
+ opts.signal
130
+ ),
131
+ deactivate: (id, opts) => adminEmbed(
132
+ "PATCH",
133
+ buildEmbedUrl(this.baseUrl, `/admin/members/${encodeURIComponent(id)}`),
134
+ opts.accessToken,
135
+ { status: "inactive" },
136
+ opts.signal
137
+ )
138
+ }
139
+ };
140
+ };
141
+ function createClient(opts) {
142
+ return new PlayaOSClient(opts);
143
+ }
144
+
145
+ // src/exchange.ts
146
+ async function exchangeCode(params) {
147
+ const base = params.authBaseUrl ?? "https://auth.playaos.app";
148
+ const res = await fetch(`${base}/api/auth/v1/exchange`, {
149
+ method: "POST",
150
+ headers: { "content-type": "application/json" },
151
+ body: JSON.stringify({
152
+ code: params.code,
153
+ code_verifier: params.codeVerifier,
154
+ client_id: params.clientId,
155
+ client_secret: params.clientSecret,
156
+ redirect_uri: params.redirectUri
157
+ })
158
+ });
159
+ let body;
160
+ try {
161
+ body = await res.json();
162
+ } catch {
163
+ throw new Error(`Exchange failed: non-JSON response (${res.status})`);
164
+ }
165
+ if (!res.ok) {
166
+ const errorReason = typeof body === "object" && body !== null && "error" in body && typeof body.error === "string" ? body.error : String(res.status);
167
+ throw new Error(`Exchange failed: ${errorReason}`);
168
+ }
169
+ if (typeof body !== "object" || body === null || !("idToken" in body) || typeof body.idToken !== "string" || !("expiresAt" in body) || typeof body.expiresAt !== "string") {
170
+ throw new Error("Exchange failed: malformed success response");
171
+ }
172
+ const refreshToken = "refreshToken" in body && typeof body.refreshToken === "string" ? body.refreshToken : void 0;
173
+ if (refreshToken === void 0) {
174
+ throw new Error("Exchange failed: missing refreshToken in confidential response");
175
+ }
176
+ return {
177
+ idToken: body.idToken,
178
+ expiresAt: body.expiresAt,
179
+ refreshToken
180
+ };
181
+ }
182
+ export {
183
+ ApiClientError,
184
+ PlayaOSClient,
185
+ createClient,
186
+ exchangeCode
187
+ };
188
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/error.ts","../src/client.ts","../src/exchange.ts"],"sourcesContent":["export class ApiClientError extends Error {\n constructor(\n public readonly status: number,\n public readonly code: string,\n message: string,\n ) {\n super(message);\n this.name = \"ApiClientError\";\n }\n}\n\nexport async function parseError(res: Response): Promise<ApiClientError> {\n try {\n const body = (await res.json()) as { error?: string; code?: string };\n return new ApiClientError(res.status, body.code ?? \"UNKNOWN\", body.error ?? res.statusText);\n } catch {\n return new ApiClientError(res.status, \"UNKNOWN\", res.statusText);\n }\n}\n","import { type ApiClientError, parseError } from \"./error.js\";\nimport type {\n Application,\n ApplicationCreatePayload,\n ApplicationCreateResponse,\n ApplicationStatus,\n ApplicationTransitionPayload,\n ApplicationTransitionResponse,\n DuesStatus,\n Member,\n MemberCreatePayload,\n MemberCreateResponse,\n MemberDeactivateResponse,\n MemberRole,\n MemberUpdatePayload,\n MemberUpdateResponse,\n OrgConfig,\n PaymentPageResponse,\n Shift,\n} from \"./types.js\";\n\nexport type { ApiClientError };\n\ninterface ClientOptions {\n /** Base URL of the PlayaOS portal, e.g. https://your-camp.playaos.app */\n baseUrl: string;\n /** API key in the format pk_live_* */\n apiKey: string;\n}\n\nfunction buildUrl(base: string, path: string, params?: Record<string, string | number | boolean | undefined>): string {\n const url = new URL(`${base}/api/v1${path}`);\n if (params) {\n for (const [k, v] of Object.entries(params)) {\n // Skip undefined and false booleans — z.coerce.boolean() on the server uses Boolean(),\n // which treats any non-empty string (including \"false\") as true. Omitting a false boolean\n // param has the same effect as the server default (unfiltered).\n if (v !== undefined && v !== false) url.searchParams.set(k, String(v));\n }\n }\n return url.toString();\n}\n\nfunction buildEmbedUrl(base: string, path: string): string {\n return `${base}/api/embed/v1${path}`;\n}\n\nasync function request<T>(url: string, apiKey: string, signal?: AbortSignal): Promise<T> {\n const res = await fetch(url, {\n headers: { Authorization: `Bearer ${apiKey}` },\n signal,\n });\n if (!res.ok) throw await parseError(res);\n return res.json() as Promise<T>;\n}\n\nasync function post<T>(url: string, apiKey: string, body: unknown, signal?: AbortSignal): Promise<T> {\n const res = await fetch(url, {\n method: \"POST\",\n headers: { Authorization: `Bearer ${apiKey}`, \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n signal,\n });\n if (!res.ok) throw await parseError(res);\n return res.json() as Promise<T>;\n}\n\nasync function postEmbed<T>(\n url: string,\n campKey: string,\n body: unknown,\n opts?: { accessToken?: string; signal?: AbortSignal },\n): Promise<T> {\n const headers: Record<string, string> = {\n \"X-Camp-Key\": campKey,\n \"Content-Type\": \"application/json\",\n };\n if (opts?.accessToken) headers.Authorization = `Bearer ${opts.accessToken}`;\n const res = await fetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n signal: opts?.signal,\n });\n if (!res.ok) throw await parseError(res);\n return res.json() as Promise<T>;\n}\n\n/**\n * Admin write helper for /api/embed/v1/admin/* (PLA-489). Bearer-only: the\n * admin token is the sole credential (no X-Camp-Key — orgId comes from the JWT\n * claims server-side). `method` is POST for create, PATCH for transition/update/\n * deactivate.\n */\nasync function adminEmbed<T>(\n method: \"POST\" | \"PATCH\",\n url: string,\n accessToken: string,\n body: unknown,\n signal?: AbortSignal,\n): Promise<T> {\n const res = await fetch(url, {\n method,\n headers: { Authorization: `Bearer ${accessToken}`, \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n signal,\n });\n if (!res.ok) throw await parseError(res);\n return res.json() as Promise<T>;\n}\n\nexport class PlayaOSClient {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n\n constructor(opts: ClientOptions) {\n this.baseUrl = opts.baseUrl.replace(/\\/$/, \"\");\n this.apiKey = opts.apiKey;\n }\n\n readonly members = {\n list: (params?: { role?: MemberRole; status?: string }, opts?: { signal?: AbortSignal }): Promise<Member[]> =>\n request(buildUrl(this.baseUrl, \"/members\", params), this.apiKey, opts?.signal),\n\n get: (id: string, opts?: { signal?: AbortSignal }): Promise<Member> =>\n request(buildUrl(this.baseUrl, `/members/${encodeURIComponent(id)}`), this.apiKey, opts?.signal),\n };\n\n readonly applications = {\n list: (\n params?: { status?: ApplicationStatus; year?: number },\n opts?: { signal?: AbortSignal },\n ): Promise<Application[]> => request(buildUrl(this.baseUrl, \"/applications\", params), this.apiKey, opts?.signal),\n\n // Anonymous external-camp submission against POST /api/embed/v1/applications\n // (PLA-481). Uses the X-Camp-Key header rather than Bearer auth. Pass\n // `accessToken` to additionally send a PlayaOS IdP JWT (PLA-573 path) so\n // the server trusts the verified profile claim and skips the email-based\n // bootstrap.\n create: (\n payload: ApplicationCreatePayload,\n opts?: { accessToken?: string; signal?: AbortSignal },\n ): Promise<ApplicationCreateResponse> =>\n postEmbed(buildEmbedUrl(this.baseUrl, \"/applications\"), this.apiKey, payload, opts),\n };\n\n readonly dues = {\n list: (params?: { userId?: string; year?: number }, opts?: { signal?: AbortSignal }): Promise<DuesStatus[]> =>\n request(buildUrl(this.baseUrl, \"/dues\", params), this.apiKey, opts?.signal),\n };\n\n readonly shifts = {\n list: (\n params?: { year?: number; fromDate?: string; toDate?: string; publishedOnly?: boolean },\n opts?: { signal?: AbortSignal },\n ): Promise<Shift[]> => request(buildUrl(this.baseUrl, \"/shifts\", params), this.apiKey, opts?.signal),\n };\n\n readonly org = {\n get: (opts?: { signal?: AbortSignal }): Promise<OrgConfig> =>\n request(buildUrl(this.baseUrl, \"/org\"), this.apiKey, opts?.signal),\n };\n\n readonly payments = {\n page: (\n memberId: string,\n duesCollectionId?: string,\n opts?: { signal?: AbortSignal },\n ): Promise<PaymentPageResponse> =>\n post(buildUrl(this.baseUrl, \"/payments/page\"), this.apiKey, { memberId, duesCollectionId }, opts?.signal),\n };\n\n // Admin write APIs (PLA-489). Each method requires `accessToken` — a Supabase\n // JWT for a camp admin/super_admin. The org boundary is enforced server-side\n // from the JWT claims; these endpoints do not use the X-Camp-Key.\n readonly admin = {\n applications: {\n transition: (\n id: string,\n payload: ApplicationTransitionPayload,\n opts: { accessToken: string; signal?: AbortSignal },\n ): Promise<ApplicationTransitionResponse> =>\n adminEmbed(\n \"PATCH\",\n buildEmbedUrl(this.baseUrl, `/admin/applications/${encodeURIComponent(id)}`),\n opts.accessToken,\n payload,\n opts.signal,\n ),\n },\n members: {\n create: (\n payload: MemberCreatePayload,\n opts: { accessToken: string; signal?: AbortSignal },\n ): Promise<MemberCreateResponse> =>\n adminEmbed(\"POST\", buildEmbedUrl(this.baseUrl, \"/admin/members\"), opts.accessToken, payload, opts.signal),\n\n update: (\n id: string,\n patch: MemberUpdatePayload,\n opts: { accessToken: string; signal?: AbortSignal },\n ): Promise<MemberUpdateResponse> =>\n adminEmbed(\n \"PATCH\",\n buildEmbedUrl(this.baseUrl, `/admin/members/${encodeURIComponent(id)}`),\n opts.accessToken,\n patch,\n opts.signal,\n ),\n\n deactivate: (\n id: string,\n opts: { accessToken: string; signal?: AbortSignal },\n ): Promise<MemberDeactivateResponse> =>\n adminEmbed(\n \"PATCH\",\n buildEmbedUrl(this.baseUrl, `/admin/members/${encodeURIComponent(id)}`),\n opts.accessToken,\n { status: \"inactive\" },\n opts.signal,\n ),\n },\n };\n}\n\n/** Convenience factory — equivalent to `new PlayaOSClient(opts)`. */\nexport function createClient(opts: ClientOptions): PlayaOSClient {\n return new PlayaOSClient(opts);\n}\n","/**\n * Server-side OAuth-code → ID-token exchange (PLA-573 T24).\n *\n * Used by camp sites' confidential-client backends to swap a one-time\n * authorization code from the PlayaOS IdP for a short-lived ID token.\n * Public/PKCE-only clients exchange directly from the browser via\n * `usePlayaOSAuth` in `@playaos/react`; this helper exists for confidential\n * clients that need to send the client_secret server-side.\n */\n\nexport interface ExchangeParams {\n authBaseUrl?: string;\n code: string;\n codeVerifier: string;\n clientId: string;\n clientSecret: string;\n redirectUri: string;\n}\n\nexport interface ExchangeResult {\n idToken: string;\n expiresAt: string;\n refreshToken: string;\n}\n\nexport async function exchangeCode(params: ExchangeParams): Promise<ExchangeResult> {\n const base = params.authBaseUrl ?? \"https://auth.playaos.app\";\n const res = await fetch(`${base}/api/auth/v1/exchange`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({\n code: params.code,\n code_verifier: params.codeVerifier,\n client_id: params.clientId,\n client_secret: params.clientSecret,\n redirect_uri: params.redirectUri,\n }),\n });\n let body: unknown;\n try {\n body = await res.json();\n } catch {\n throw new Error(`Exchange failed: non-JSON response (${res.status})`);\n }\n\n if (!res.ok) {\n const errorReason =\n typeof body === \"object\" && body !== null && \"error\" in body && typeof body.error === \"string\"\n ? body.error\n : String(res.status);\n throw new Error(`Exchange failed: ${errorReason}`);\n }\n\n if (\n typeof body !== \"object\" ||\n body === null ||\n !(\"idToken\" in body) ||\n typeof body.idToken !== \"string\" ||\n !(\"expiresAt\" in body) ||\n typeof body.expiresAt !== \"string\"\n ) {\n throw new Error(\"Exchange failed: malformed success response\");\n }\n\n // refreshToken is optional in the response (public clients don't get one);\n // surface as undefined when absent. Result type's refreshToken is required\n // because exchangeCode is the confidential-client helper — keep it required\n // in the typed return but pull it defensively in case the server omits it.\n const refreshToken = \"refreshToken\" in body && typeof body.refreshToken === \"string\" ? body.refreshToken : undefined;\n\n if (refreshToken === undefined) {\n throw new Error(\"Exchange failed: missing refreshToken in confidential response\");\n }\n\n return {\n idToken: body.idToken,\n expiresAt: body.expiresAt,\n refreshToken,\n };\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@playaos/api-client",
3
+ "version": "0.1.0",
4
+ "description": "Typed API client for PlayaOS — manage camp members, dues, shifts, and applications",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./schemas": {
14
+ "types": "./dist/applications-schema.d.ts",
15
+ "import": "./dist/applications-schema.js"
16
+ }
17
+ },
18
+ "files": ["dist", "README.md"],
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "dev": "tsup --watch",
22
+ "typecheck": "tsc --noEmit",
23
+ "test": "vitest run"
24
+ },
25
+ "keywords": ["playaos", "burning-man", "camp-management", "api-client"],
26
+ "license": "MIT",
27
+ "peerDependencies": {
28
+ "zod": "^4.4.3"
29
+ },
30
+ "peerDependenciesMeta": {
31
+ "zod": {
32
+ "optional": true
33
+ }
34
+ },
35
+ "devDependencies": {
36
+ "tsup": "^8.5.1",
37
+ "typescript": "^5.9.3",
38
+ "vitest": "^4.1.5",
39
+ "zod": "^4.4.3"
40
+ }
41
+ }