File size: 16,442 Bytes
bcce530
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
import { Metadata } from 'next'
import { generateSEO } from '@/lib/seo'
import { Badge } from '@/components/ui/badge'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'

const BASE_URL = process.env.NEXT_PUBLIC_APP_URL || 'https://open-prompt.netlify.app'

export const metadata: Metadata = generateSEO({
    title: 'API Documentation | OpenPrompt',
    description: 'OpenPrompt Public REST API — run AI prompts and access 177+ AI tools programmatically. No signup required for public endpoints.',
    url: `${BASE_URL}/docs/api`,
    keywords: ['OpenPrompt API', 'AI prompts API', 'REST API', 'prompt API', 'AI tools API'],
}) as Metadata

const endpoints = [
    {
        method: 'GET',
        path: '/api/v1',
        description: 'API overview — lists all endpoints, rate limits, and authentication info.',
        response: '{ name, version, endpoints[], rateLimits }',
        auth: false,
    },
    {
        method: 'GET',
        path: '/api/v1/prompts',
        description: 'List public prompts with search, filter, sort, and pagination.',
        params: [
            { name: 'q', type: 'string', desc: 'Search term (title, description, tags)' },
            { name: 'category', type: 'string', desc: 'Filter by category' },
            { name: 'tags', type: 'string', desc: 'Comma-separated tag filter' },
            { name: 'sort', type: 'runs | stars | newest', desc: 'Sort order (default: newest)' },
            { name: 'page', type: 'number', desc: 'Page number (default: 1)' },
            { name: 'limit', type: 'number', desc: 'Results per page (max 100, default 20)' },
        ],
        response: '{ data: Prompt[], pagination: { page, limit, total, totalPages } }',
        auth: false,
    },
    {
        method: 'GET',
        path: '/api/v1/prompts/:slug',
        description: 'Retrieve a single public prompt by its slug, including template and schema.',
        response: '{ data: Prompt }',
        auth: false,
    },
    {
        method: 'POST',
        path: '/api/v1/prompts/:slug/run',
        description: 'Execute a prompt with variable substitution. Supports streaming.',
        body: [
            { name: 'variables', type: 'object', desc: 'Key-value pairs matching the prompt schema' },
            { name: 'model', type: 'string', desc: 'Model override (must be in prompt\'s modelAllowed)' },
            { name: 'stream', type: 'boolean', desc: 'Stream response as text/plain (default: false)' },
        ],
        response: '{ output: string, model: string, cached: boolean }',
        auth: false,
        rateLimit: '10/hr guest · 50/hr authenticated · 500/hr Pro',
    },
    {
        method: 'GET',
        path: '/api/v1/tools',
        description: 'List all 177+ AI tools with name, description, category, and icon.',
        params: [
            { name: 'category', type: 'string', desc: 'Filter by category' },
            { name: 'q', type: 'string', desc: 'Search by name or description' },
            { name: 'limit', type: 'number', desc: 'Max results (default 50, max 200)' },
        ],
        response: '{ data: Tool[], total, totalAll }',
        auth: false,
    },
]

const codeExamples = {
    listPrompts: `// List top prompts for "marketing"
const response = await fetch(
  'https://open-prompt.netlify.app/api/v1/prompts?category=marketing&sort=stars&limit=10'
)
const { data, pagination } = await response.json()
console.log(\`Found \${pagination.total} prompts\`)`,

    runPrompt: `// Execute the "blog-writer" prompt
const response = await fetch(
  'https://open-prompt.netlify.app/api/v1/prompts/blog-writer/run',
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      variables: {
        topic: 'The Future of AI Agents',
        tone: 'professional',
        length: '800 words',
      },
      model: 'gemini-2.0-flash',
    }),
  }
)
const { output } = await response.json()
console.log(output)`,

    streamPrompt: `// Stream a prompt response in real-time
const response = await fetch(
  'https://open-prompt.netlify.app/api/v1/prompts/blog-writer/run',
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      variables: { topic: 'AI trends 2026' },
      stream: true,  // ← enable streaming
    }),
  }
)

const reader = response.body.getReader()
const decoder = new TextDecoder()
while (true) {
  const { done, value } = await reader.read()
  if (done) break
  process.stdout.write(decoder.decode(value))
}`,

    listTools: `// Search for email-related tools
const response = await fetch(
  'https://open-prompt.netlify.app/api/v1/tools?q=email&limit=5'
)
const { data } = await response.json()
data.forEach(tool => console.log(\`\${tool.icon} \${tool.name}\`))`,
}

const methodColors: Record<string, string> = {
    GET: 'bg-green-500/15 text-green-600 dark:text-green-400 border-green-500/30',
    POST: 'bg-blue-500/15 text-blue-600 dark:text-blue-400 border-blue-500/30',
    PUT: 'bg-amber-500/15 text-amber-600 dark:text-amber-400 border-amber-500/30',
    DELETE: 'bg-red-500/15 text-red-600 dark:text-red-400 border-red-500/30',
}

export default function ApiDocsPage() {
    return (
        <div className="container mx-auto px-4 py-12 max-w-5xl">
            {/* Header */}
            <div className="mb-12">
                <div className="flex items-center gap-3 mb-4">
                    <h1 className="text-4xl font-serif font-medium">API Documentation</h1>
                    <Badge variant="secondary">v1.0</Badge>
                    <Badge className="bg-green-500/15 text-green-600 border-green-500/30 border">
                        Public Beta
                    </Badge>
                </div>
                <p className="text-muted-foreground text-lg max-w-3xl">
                    OpenPrompt&apos;s public REST API lets you run prompts, execute AI tools, and browse
                    the prompt library programmatically. No API key required for public endpoints.
                </p>
            </div>

            {/* Base URL */}
            <Card className="mb-10 border-primary/30">
                <CardContent className="p-6">
                    <div className="flex flex-wrap items-center gap-4">
                        <div>
                            <p className="text-xs font-medium text-muted-foreground uppercase tracking-wider mb-1">Base URL</p>
                            <code className="text-primary font-mono text-lg">
                                {BASE_URL}/api/v1
                            </code>
                        </div>
                        <div className="h-8 w-px bg-border hidden md:block" />
                        <div>
                            <p className="text-xs font-medium text-muted-foreground uppercase tracking-wider mb-1">Rate Limits</p>
                            <p className="text-sm">
                                <span className="font-medium">10 req/hr</span> guest ·{' '}
                                <span className="font-medium">50 req/hr</span> authenticated ·{' '}
                                <span className="font-medium text-primary">500 req/hr</span> Pro
                            </p>
                        </div>
                        <div className="h-8 w-px bg-border hidden md:block" />
                        <div>
                            <p className="text-xs font-medium text-muted-foreground uppercase tracking-wider mb-1">Authentication</p>
                            <p className="text-sm text-muted-foreground">Not required for public data</p>
                        </div>
                    </div>
                </CardContent>
            </Card>

            {/* Endpoints */}
            <section className="mb-12">
                <h2 className="text-2xl font-serif font-medium mb-6">Endpoints</h2>
                <div className="space-y-4">
                    {endpoints.map((ep) => (
                        <Card key={`${ep.method}-${ep.path}`} className="overflow-hidden">
                            <CardHeader className="pb-3 pt-4 px-6">
                                <div className="flex items-start gap-3 flex-wrap">
                                    <span className={`text-xs font-bold px-2.5 py-1 rounded border font-mono ${methodColors[ep.method]}`}>
                                        {ep.method}
                                    </span>
                                    <code className="font-mono text-sm text-foreground bg-muted px-3 py-1 rounded-md">
                                        {ep.path}
                                    </code>
                                    {!ep.auth && (
                                        <Badge variant="outline" className="text-xs ml-auto">No auth</Badge>
                                    )}
                                </div>
                                <p className="text-muted-foreground text-sm mt-2">{ep.description}</p>
                            </CardHeader>
                            <CardContent className="px-6 pb-4 space-y-4">
                                {ep.params && ep.params.length > 0 && (
                                    <div>
                                        <p className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2">
                                            Query Parameters
                                        </p>
                                        <div className="rounded-lg border overflow-hidden">
                                            {ep.params.map((p, i) => (
                                                <div
                                                    key={p.name}
                                                    className={`flex items-start gap-3 px-4 py-2.5 text-sm ${
                                                        i % 2 === 0 ? 'bg-muted/30' : ''
                                                    }`}
                                                >
                                                    <code className="font-mono text-primary font-medium w-24 shrink-0">{p.name}</code>
                                                    <code className="text-muted-foreground text-xs w-32 shrink-0 mt-0.5">{p.type}</code>
                                                    <span className="text-muted-foreground">{p.desc}</span>
                                                </div>
                                            ))}
                                        </div>
                                    </div>
                                )}
                                {ep.body && ep.body.length > 0 && (
                                    <div>
                                        <p className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2">
                                            Request Body (JSON)
                                        </p>
                                        <div className="rounded-lg border overflow-hidden">
                                            {ep.body.map((p, i) => (
                                                <div
                                                    key={p.name}
                                                    className={`flex items-start gap-3 px-4 py-2.5 text-sm ${
                                                        i % 2 === 0 ? 'bg-muted/30' : ''
                                                    }`}
                                                >
                                                    <code className="font-mono text-primary font-medium w-24 shrink-0">{p.name}</code>
                                                    <code className="text-muted-foreground text-xs w-32 shrink-0 mt-0.5">{p.type}</code>
                                                    <span className="text-muted-foreground">{p.desc}</span>
                                                </div>
                                            ))}
                                        </div>
                                    </div>
                                )}
                                <div>
                                    <p className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2">
                                        Response
                                    </p>
                                    <code className="text-xs font-mono bg-muted px-3 py-2 rounded-md block text-muted-foreground">
                                        {ep.response}
                                    </code>
                                </div>
                                {ep.rateLimit && (
                                    <p className="text-xs text-muted-foreground">
                                        <span className="font-medium">Rate limit:</span> {ep.rateLimit}
                                    </p>
                                )}
                            </CardContent>
                        </Card>
                    ))}
                </div>
            </section>

            {/* Code Examples */}
            <section className="mb-12">
                <h2 className="text-2xl font-serif font-medium mb-6">Code Examples</h2>
                <div className="space-y-6">
                    {[
                        { title: 'List prompts by category', code: codeExamples.listPrompts },
                        { title: 'Run a prompt with variables', code: codeExamples.runPrompt },
                        { title: 'Stream a prompt response', code: codeExamples.streamPrompt },
                        { title: 'Search AI tools', code: codeExamples.listTools },
                    ].map(({ title, code }) => (
                        <div key={title}>
                            <h3 className="text-sm font-semibold mb-2 text-muted-foreground uppercase tracking-wide">
                                {title}
                            </h3>
                            <pre className="bg-[#0d1117] text-[#e6edf3] rounded-xl p-5 overflow-x-auto text-sm font-mono leading-relaxed border border-border/50">
                                <code>{code}</code>
                            </pre>
                        </div>
                    ))}
                </div>
            </section>

            {/* Rate Limit Headers */}
            <section className="mb-12">
                <h2 className="text-2xl font-serif font-medium mb-4">Rate Limit Headers</h2>
                <p className="text-muted-foreground mb-4">
                    Every API response includes rate limit information in the response headers:
                </p>
                <div className="rounded-lg border overflow-hidden">
                    {[
                        { header: 'X-RateLimit-Limit', desc: 'Maximum requests allowed in the current window' },
                        { header: 'X-RateLimit-Remaining', desc: 'Requests remaining in the current window' },
                        { header: 'X-RateLimit-Reset', desc: 'Unix timestamp when the window resets' },
                        { header: 'X-Cache', desc: 'HIT if served from cache, MISS if freshly generated' },
                    ].map((h, i) => (
                        <div
                            key={h.header}
                            className={`flex items-center gap-4 px-4 py-3 text-sm ${i % 2 === 0 ? 'bg-muted/30' : ''}`}
                        >
                            <code className="font-mono text-primary font-medium w-52 shrink-0">{h.header}</code>
                            <span className="text-muted-foreground">{h.desc}</span>
                        </div>
                    ))}
                </div>
            </section>

            {/* Coming Soon */}
            <Card className="border-primary/20 bg-primary/5">
                <CardContent className="p-6">
                    <h3 className="font-semibold mb-2">🔑 API Keys Coming Soon</h3>
                    <p className="text-muted-foreground text-sm">
                        Pro users will receive API keys for higher rate limits (500 req/hr) and access
                        to private prompts. Subscribe to Pro to be first in line.
                    </p>
                </CardContent>
            </Card>
        </div>
    )
}