Customers API

Customers represent users of your application. Each customer:

  • Has an external ID (your system’s user identifier)
  • Can have custom metadata
  • May have a subscription to a tier (which defines model access and usage limits)

Quick Reference

GET    /api/v1/customers              List customers
POST   /api/v1/customers              Create customer
GET    /api/v1/customers/:id          Get customer
PUT    /api/v1/customers              Upsert customer by external_id
DELETE /api/v1/customers/:id          Delete customer
POST   /api/v1/customers/:id/subscribe Create checkout session
GET    /api/v1/customers/:id/subscription Get subscription status
POST   /api/v1/customers/claim        Link identity to customer

Authentication

Customer management endpoints require a secret key (mr_sk_*).

The /customers/claim endpoint also accepts publishable keys for user self-service flows.

List Customers

GET /api/v1/customers

Returns all customers in the project.

If a customer is not subscribed, the subscription field is null.

Response

{
  "customers": [
    {
      "customer": {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "project_id": "550e8400-e29b-41d4-a716-446655440001",
        "external_id": "user_123",
        "email": "alice@example.com",
        "metadata": {
          "plan": "monthly",
          "referral_code": "FRIEND10"
        },
        "created_at": "2024-01-01T10:30:00Z",
        "updated_at": "2024-01-15T14:22:00Z"
      },
      "subscription": {
        "id": "550e8400-e29b-41d4-a716-446655440010",
        "project_id": "550e8400-e29b-41d4-a716-446655440001",
        "customer_id": "550e8400-e29b-41d4-a716-446655440000",
        "tier_id": "550e8400-e29b-41d4-a716-446655440002",
        "tier_code": "pro",
        "subscription_status": "active",
        "current_period_start": "2024-01-01T00:00:00Z",
        "current_period_end": "2024-02-01T00:00:00Z",
        "created_at": "2024-01-01T10:30:00Z",
        "updated_at": "2024-01-15T14:22:00Z"
      }
    }
  ]
}

Example

curl https://api.modelrelay.ai/api/v1/customers \
  -H "Authorization: Bearer mr_sk_..."
import { ModelRelay } from "@modelrelay/sdk";

const mr = ModelRelay.fromSecretKey(process.env.MODELRELAY_SECRET_KEY!);

const customers = await mr.customers.list();
for (const entry of customers) {
  const tier = entry.subscription?.tier_code ?? "none";
  console.log(`${entry.customer.external_id}: ${tier}`);
}
customers, err := client.Customers.List(ctx)
if err != nil {
    return err
}

for _, entry := range customers {
    tier := "none"
    if entry.Subscription != nil {
        tier = entry.Subscription.TierCode.String()
    }
    fmt.Printf("%s: %s\n", entry.Customer.ExternalID, tier)
}

Create Customer

POST /api/v1/customers

Creates a new customer in the project.

Request Body

Field Type Required Description
external_id string Yes Your system’s user identifier (1-255 chars)
email string Yes Customer email address
metadata object No Custom key-value data (max 10KB)

Response

Returns the created customer object wrapped in { "customer": ... }.

Example

curl -X POST https://api.modelrelay.ai/api/v1/customers \
  -H "Authorization: Bearer mr_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "user_456",
    "email": "bob@example.com",
    "metadata": {
      "signup_source": "landing_page"
    }
  }'
const customer = await mr.customers.create({
  external_id: "user_456",
  email: "bob@example.com",
  metadata: {
    signup_source: "landing_page",
  },
});

console.log(`Created customer: ${customer.customer.id}`);
customer, err := client.Customers.Create(ctx, sdk.CustomerCreateRequest{
    ExternalID: sdk.NewCustomerExternalID("user_456"),
    Email:      "bob@example.com",
    Metadata: sdk.CustomerMetadata{
        "signup_source": "landing_page",
    },
})
if err != nil {
    return err
}

fmt.Printf("Created customer: %s\n", customer.Customer.ID)

Get Customer

GET /api/v1/customers/:id

Returns a single customer by ID.

Example

curl https://api.modelrelay.ai/api/v1/customers/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer mr_sk_..."
const customer = await mr.customers.get("550e8400-e29b-41d4-a716-446655440000");
const tier = customer.subscription?.tier_code ?? "none";
console.log(`${customer.customer.external_id} is on the ${tier} tier`);
customer, err := client.Customers.Get(ctx, uuid.MustParse("550e8400-e29b-41d4-a716-446655440000"))
if err != nil {
    return err
}

tier := "none"
if customer.Subscription != nil {
    tier = customer.Subscription.TierCode.String()
}
fmt.Printf("%s is on the %s tier\n", customer.Customer.ExternalID, tier)

Upsert Customer

PUT /api/v1/customers

Creates or updates a customer by external_id. If a customer with the given external_id exists, it is updated. Otherwise, a new customer is created.

This is useful for syncing customers from your user database without checking if they exist first.

Request Body

Field Type Required Description
external_id string Yes Your system’s user identifier
email string Yes Customer email address
metadata object No Custom key-value data

Example

curl -X PUT https://api.modelrelay.ai/api/v1/customers \
  -H "Authorization: Bearer mr_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "user_456",
    "email": "bob@newdomain.com"
  }'
// Creates if not exists, updates if exists
const customer = await mr.customers.upsert({
  external_id: "user_456",
  email: "bob@newdomain.com",
});
customer, err := client.Customers.Upsert(ctx, sdk.CustomerUpsertRequest{
    ExternalID: sdk.NewCustomerExternalID("user_456"),
    Email:      "bob@newdomain.com",
})

Delete Customer

DELETE /api/v1/customers/:id

Removes a customer. This does not cancel their Stripe subscription automatically.

Example

curl -X DELETE https://api.modelrelay.ai/api/v1/customers/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer mr_sk_..."
await mr.customers.delete("550e8400-e29b-41d4-a716-446655440000");
err := client.Customers.Delete(ctx, uuid.MustParse("550e8400-e29b-41d4-a716-446655440000"))

Create Checkout Session

POST /api/v1/customers/:id/subscribe

Creates a Stripe checkout session for an existing customer to subscribe to their tier’s paid plan.

Request Body

Field Type Required Description
tier_id string Yes Tier to subscribe the customer to
success_url string Yes URL to redirect after successful payment
cancel_url string Yes URL to redirect if user cancels

Response

Field Type Description
session_id string Stripe checkout session ID
url string URL to redirect user to Stripe checkout

Example

curl -X POST https://api.modelrelay.ai/api/v1/customers/550e8400-e29b-41d4-a716-446655440000/subscribe \
  -H "Authorization: Bearer mr_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "tier_id": "550e8400-e29b-41d4-a716-446655440111",
    "success_url": "https://myapp.com/success",
    "cancel_url": "https://myapp.com/billing"
  }'
const session = await mr.customers.subscribe(
  "550e8400-e29b-41d4-a716-446655440000",
  {
    tier_id: "550e8400-e29b-41d4-a716-446655440111",
    success_url: "https://myapp.com/success",
    cancel_url: "https://myapp.com/billing",
  }
);

// Redirect user to Stripe
res.redirect(session.url);
session, err := client.Customers.Subscribe(ctx,
    uuid.MustParse("550e8400-e29b-41d4-a716-446655440000"),
    sdk.CustomerSubscribeRequest{
        TierID:     uuid.MustParse("550e8400-e29b-41d4-a716-446655440111"),
        SuccessURL: "https://myapp.com/success",
        CancelURL:  "https://myapp.com/billing",
    },
)
if err != nil {
    return err
}

http.Redirect(w, r, session.URL, http.StatusSeeOther)

Get Subscription Status

GET /api/v1/customers/:id/subscription

Returns the Stripe subscription status for a customer.

Response

Field Type Description
active boolean Whether the subscription is active
subscription_id string Stripe subscription ID
status string Subscription status (active, trialing, past_due, etc.)
current_period_start string Start of current billing period (ISO 8601)
current_period_end string End of current billing period (ISO 8601)

Example

curl https://api.modelrelay.ai/api/v1/customers/550e8400-e29b-41d4-a716-446655440000/subscription \
  -H "Authorization: Bearer mr_sk_..."
const subscription = await mr.customers.getSubscription(
  "550e8400-e29b-41d4-a716-446655440000"
);

if (subscription.active) {
  console.log(`Active until ${subscription.current_period_end}`);
}
sub, err := client.Customers.GetSubscription(ctx,
    uuid.MustParse("550e8400-e29b-41d4-a716-446655440000"),
)
if err != nil {
    return err
}

if sub.Active {
    fmt.Printf("Active until %s\n", sub.CurrentPeriodEnd)
}

Claim Customer

POST /api/v1/customers/claim

Links a customer identity (OAuth provider + subject) to a customer found by email. Used when a customer subscribes via Stripe Checkout (email only) and later authenticates to your app.

This endpoint works with both publishable and secret keys, enabling user self-service flows.

Request Body

Field Type Required Description
email string Yes Email to match the customer
provider string Yes OAuth provider (e.g., github, google)
subject string Yes Provider’s user identifier

Response

Returns the claimed customer object.

Example

curl -X POST https://api.modelrelay.ai/api/v1/customers/claim \
  -H "Authorization: Bearer mr_pk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "email": "alice@example.com",
    "provider": "github",
    "subject": "12345678"
  }'
// After user authenticates with OAuth
const customer = await mr.customers.claim({
  email: user.email,
  provider: "github",
  subject: user.id,
});
customer, err := client.Customers.Claim(ctx, sdk.CustomerClaimRequest{
    Email:    user.Email,
    Provider: sdk.CustomerIdentityProvider("github"),
    Subject:  sdk.CustomerIdentitySubject(user.ID),
})

Customer Object

Field Type Description
id uuid Unique customer identifier
project_id uuid Project this customer belongs to
external_id string Your system’s user identifier
email string Customer email address
metadata object Custom key-value data
created_at datetime When the customer was created
updated_at datetime When the customer was last modified

Subscription Object

Field Type Description
id uuid Unique subscription identifier
project_id uuid Project this subscription belongs to
customer_id uuid Customer this subscription is attached to
tier_id uuid Tier this subscription grants access to
tier_code string Tier code (e.g., free, pro)
billing_provider string Billing provider (stripe, crypto, app_store, external)
billing_customer_id string Billing customer ID from the provider
billing_subscription_id string Billing subscription ID from the provider
subscription_status string Subscription status
current_period_start datetime Start of current billing period
current_period_end datetime End of current billing period
created_at datetime When the subscription was created
updated_at datetime When the subscription was last modified

Subscription Status Values

Status Description
active Subscription is active and paid
trialing Customer is in free trial period
past_due Payment failed, grace period active
canceled Subscription was canceled
unpaid Payment failed, subscription suspended
incomplete Initial payment pending
incomplete_expired Initial payment expired
paused Subscription paused

Metadata

Customer metadata allows storing arbitrary key-value data with each customer. Common use cases:

  • User preferences
  • Feature flags
  • Referral tracking
  • Integration IDs

Constraints

Constraint Limit
Total size 10 KB
Key length 40 characters
Nesting depth 5 levels
Value types string, number, boolean, null, array, object

Example

{
  "metadata": {
    "plan": "annual",
    "referral_code": "FRIEND10",
    "feature_flags": {
      "beta_access": true,
      "dark_mode": false
    },
    "integrations": ["slack", "github"]
  }
}

Customer Tokens

To make API requests on behalf of a customer (for /responses, /runs endpoints), mint a customer token:

POST /api/v1/auth/customer-token

See Authentication for details on customer tokens.

Error Codes

Status Description
400 Invalid request (missing fields, invalid email)
401 Missing or invalid authentication
404 Customer not found
409 Identity already linked to a different customer (claim)

Next Steps