Skip to main content

Source: ocean/docs/api/graphql-reference.md | ✏️ Edit on GitHub

GraphQL API Reference

Endpoint: https://ocean.goldfish.io/api/graphql (Production)
Local: http://127.0.0.1:54321/functions/v1/graphql-v2

Authentication: JWT Bearer token required for all operations except introspection.

Authentication

Headers

Authorization: Bearer <jwt_token>
Content-Type: application/json

Get JWT Token

import { supabaseClient } from '@/lib/supabase'

const {
data: { session },
} = await supabaseClient.auth.getSession()
const token = session?.access_token

Core Queries

System Status

query SystemStatus {
system_status {
healthy
timestamp
environment
cache_enabled
}
}

My Organizations

query MyOrganizations {
my_organizations {
id
name
slug
plan
stripe_subscription_status
billing_email
created_at
updated_at
}
}

Organization Details

query Organization($id: ID!) {
organization(id: $id) {
id
name
slug
plan
owner_id

# Stripe fields
stripe_customer_id
stripe_subscription_id
stripe_subscription_status
stripe_price_id
default_payment_method_id

# Billing
billing_email
tax_id
billing_address

# Subscription info
current_period_start
current_period_end
cancel_at_period_end

# Database info
neon_database_url
neon_project_id
database_ready

created_at
updated_at
}
}

Organization Members

query OrganizationMembers($organization_id: ID!) {
organization_members(organization_id: $organization_id) {
id
user_id
organization_id
role
created_at

# User profile info
user {
id
email
full_name
}
}
}

Billing Queries

Available Products

query AvailableProducts {
available_products {
id
name
description
active
default_price_id

prices {
id
unit_amount
currency
recurring_interval
recurring_interval_count
}
}
}

Billing Details

query BillingDetails {
billing_details {
subscription {
id
status
current_period_start
current_period_end
cancel_at_period_end
trial_end

price {
id
unit_amount
currency
recurring_interval

product {
id
name
description
}
}
}

customer {
id
email
default_source
balance
}

payment_methods {
id
type
card {
brand
last4
exp_month
exp_year
}
}

upcoming_invoice {
id
amount_due
currency
period_start
period_end
}
}
}

Payment Methods

query PaymentMethods {
payment_methods {
id
type
created

card {
brand
last4
exp_month
exp_year
funding
}

billing_details {
name
email
phone
}
}
}

Invoices

query Invoices($limit: Int = 10, $offset: Int = 0) {
invoices(limit: $limit, offset: $offset) {
edges {
id
number
status
amount_paid
amount_due
currency
created
period_start
period_end
hosted_invoice_url
invoice_pdf

lines {
id
description
amount
quantity
period_start
period_end

price {
id
unit_amount
currency

product {
name
description
}
}
}
}

page_info {
has_next_page
has_previous_page
total_count
}
}
}

Usage Metrics

query UsageMetrics($start_date: String, $end_date: String) {
usage_metrics(start_date: $start_date, end_date: $end_date) {
database_storage_mb
api_requests
data_transfer_gb
compute_hours

period_start
period_end

breakdown {
metric_name
current_value
previous_value
percentage_change
}
}
}

Core Mutations

Sign Up (Organization Creation)

mutation SignUp($input: SignUpInput!) {
signUp(input: $input) {
success
organization_id
message
errors
}
}

# Input type
input SignUpInput {
organization_name: String!
user_email: String!
plan: String = "free"
}

Update Organization

mutation UpdateOrganization($id: ID!, $input: UpdateOrganizationInput!) {
updateOrganization(id: $id, input: $input) {
id
name
slug
billing_email
tax_id
billing_address
}
}

# Input type
input UpdateOrganizationInput {
name: String
billing_email: String
tax_id: String
billing_address: JSON
}

Billing Mutations

Create Subscription

mutation CreateSubscription($input: CreateSubscriptionInput!) {
create_subscription(input: $input) {
success
subscription_id
client_secret
status
message
errors
}
}

# Input type
input CreateSubscriptionInput {
price_id: String!
payment_method_id: String
promotion_code: String
}

Update Subscription

mutation UpdateSubscription($input: UpdateSubscriptionInput!) {
update_subscription(input: $input) {
success
subscription_id
status
message
errors
}
}

# Input type
input UpdateSubscriptionInput {
price_id: String
proration_behavior: String = "create_prorations"
}

Cancel Subscription

mutation CancelSubscription($immediately: Boolean = false) {
cancel_subscription(immediately: $immediately) {
success
subscription_id
status
canceled_at
message
errors
}
}

Create Setup Intent (for payment methods)

mutation CreateSetupIntent {
create_setup_intent {
client_secret
status
}
}

Attach Payment Method

mutation AttachPaymentMethod($payment_method_id: String!) {
attach_payment_method(payment_method_id: $payment_method_id) {
id
type
card {
brand
last4
exp_month
exp_year
}
}
}

Customer Portal Session

mutation CreateCustomerPortalSession($return_url: String!) {
create_customer_portal_session(return_url: $return_url) {
url
}
}

Provisioning Mutations

Provision User Resources

mutation ProvisionUserResources($email: String!, $metadata: JSON) {
provision_user_resources(email: $email, metadata: $metadata) {
success
organization_id
neon_project_id
neon_database_url
message
errors
}
}

Retry Provisioning

mutation RetryProvisioning($organization_id: ID!) {
retry_provisioning(organization_id: $organization_id) {
success
organization_id
neon_project_id
neon_database_url
message
errors
}
}

Error Handling

Standard Error Response

interface GraphQLError {
message: string
locations?: Array<{
line: number
column: number
}>
path?: Array<string | number>
extensions?: {
code: string
exception?: {
stacktrace?: string[]
}
}
}

Common Error Codes

  • UNAUTHENTICATED - No valid JWT token provided
  • FORBIDDEN - User lacks required permissions
  • NOT_FOUND - Resource doesn't exist or user can't access it
  • VALIDATION_ERROR - Input validation failed
  • STRIPE_ERROR - Stripe API error
  • DATABASE_ERROR - Database operation failed
  • PROVISIONING_ERROR - Resource provisioning failed

Error Handling Example

import { request } from 'graphql-request'

try {
const data = await request('/api/graphql', query, variables, {
Authorization: `Bearer ${token}`,
})
} catch (error) {
if (error.response?.errors) {
error.response.errors.forEach((gqlError: GraphQLError) => {
console.error(`GraphQL Error: ${gqlError.message}`)

switch (gqlError.extensions?.code) {
case 'UNAUTHENTICATED':
// Redirect to login
break
case 'STRIPE_ERROR':
// Show billing error message
break
default:
// Show generic error
}
})
}
}

Usage with React Query

Query Hook Example

import { useQuery } from '@tanstack/react-query'
import { graphqlClient } from '@/lib/graphql'

const MY_ORGANIZATIONS_QUERY = `
query MyOrganizations {
my_organizations {
id
name
slug
plan
}
}
`

export const useMyOrganizations = () => {
return useQuery({
queryKey: ['organizations'],
queryFn: async () => {
const data = await graphqlClient.request(MY_ORGANIZATIONS_QUERY)
return data.my_organizations
},
})
}

Mutation Hook Example

import { useMutationWithToast } from '@/hooks/use-mutation-with-toast'
import { graphqlClient } from '@/lib/graphql'

const UPDATE_ORGANIZATION_MUTATION = `
mutation UpdateOrganization($id: ID!, $input: UpdateOrganizationInput!) {
updateOrganization(id: $id, input: $input) {
id
name
billing_email
}
}
`

export const useUpdateOrganization = () => {
return useMutationWithToast({
mutationFn: async ({ id, input }: { id: string; input: any }) => {
const data = await graphqlClient.request(UPDATE_ORGANIZATION_MUTATION, { id, input })
return data.updateOrganization
},
successMessage: 'Organization updated successfully!',
invalidateQueries: [['organization', id], ['organizations']],
})
}

GraphQL Client Setup

Basic Client

// lib/graphql.ts
import { GraphQLClient } from 'graphql-request'
import { supabaseClient } from './supabase'

const endpoint =
process.env.NODE_ENV === 'development'
? 'http://127.0.0.1:54321/functions/v1/graphql-v2'
: 'https://ocean.goldfish.io/api/graphql'

export const graphqlClient = new GraphQLClient(endpoint, {
requestMiddleware: async (request) => {
const {
data: { session },
} = await supabaseClient.auth.getSession()

if (session?.access_token) {
request.headers = {
...request.headers,
Authorization: `Bearer ${session.access_token}`,
}
}

return request
},
})

Introspection

Get Schema

# Using GraphQL CLI
npx get-graphql-schema https://ocean.goldfish.io/api/graphql > schema.graphql

# Using curl
curl -X POST https://ocean.goldfish.io/api/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ __schema { types { name } } }"}'

Code Generation

# Generate TypeScript types
pnpm run codegen:graphql

This generates typed hooks and operations in src/generated/graphql.ts.

Rate Limiting

  • Query rate limit: 1000 requests per minute per user
  • Mutation rate limit: 100 requests per minute per user
  • Billing operations: 10 requests per minute per user
  • Provisioning operations: 5 requests per hour per organization

Rate limit headers included in responses:

  • X-RateLimit-Limit: Request limit
  • X-RateLimit-Remaining: Remaining requests
  • X-RateLimit-Reset: Reset time (Unix timestamp)

Best Practices

Query Optimization

  • Request only fields you need
  • Use fragments for repeated field sets
  • Batch related queries when possible
  • Cache frequently accessed data

Error Handling

  • Always handle GraphQL errors
  • Show user-friendly error messages
  • Log errors for debugging
  • Implement retry logic for transient failures

Security

  • Never expose JWT tokens in URLs or logs
  • Validate all inputs on frontend and backend
  • Use HTTPS in production
  • Implement proper CORS policies