openapi: 3.1.0
info:
  title: Phosra API
  description: Universal Parental Controls API - Define once, push everywhere
  version: "1.0.0"
  contact:
    name: Phosra Developer Support
    url: https://docs.phosra.com
  license:
    name: Proprietary
    url: https://phosra.com/terms
servers:
- url: https://phosra-api-sandbox-production.up.railway.app/api/v1
  description: Hosted sandbox — the one canonical, stable, partner-facing testing endpoint (seeded demo family + phosra_test_ keys). Default so the inline Try it never touches production. Same base the CLI uses.
- url: https://prodapi.phosra.com/api/v1
  description: Production
- url: http://localhost:8080/api/v1
  description: Local development

security:
- BearerAuth: []

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
    DeviceKeyAuth:
      type: apiKey
      in: header
      name: X-Device-Key
      description: Device API key issued at registration (stored in iOS Keychain)

  schemas:
    Error:
      type: object
      properties:
        error:
          type: string
          description: Short, stable, machine-readable error class — matches the HTTP status text (e.g. `Not Found`).
        message:
          type: string
          description: Human-readable explanation of what went wrong and, where possible, how to fix it.
        code:
          type: integer

          description: Numeric HTTP status code, duplicated in the body for convenience.
      description: Standard error envelope returned for every 4xx and 5xx response.
      example:
        error: Not Found
        message: policy not found
        code: 404
      additionalProperties: true
    User:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this resource.
        email:
          type: string
          format: email
          description: Email address.
        name:
          type: string
          description: Human-readable display name.
        created_at:
          type: string
          format: date-time

          description: RFC 3339 timestamp of when the resource was created.
      additionalProperties: true
    TokenPair:
      type: object
      properties:
        access_token:
          type: string
          description: Short-lived bearer token sent in the `Authorization` header on subsequent requests.
        refresh_token:
          type: string
          description: Long-lived token used to obtain a new access token when the current one expires.
        expires_at:
          type: string
          format: date-time

          description: RFC 3339 timestamp of when this value expires.
      additionalProperties: true
    Family:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this resource.
        name:
          type: string
          description: Human-readable display name.
        created_at:
          type: string
          format: date-time

          description: RFC 3339 timestamp of when the resource was created.
      additionalProperties: true
    FamilyMember:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this resource.
        family_id:
          type: string
          format: uuid
          description: Identifier of the family this resource belongs to.
        user_id:
          type: string
          format: uuid
          description: Identifier of the user.
        role:
          type: string
          enum: [owner, parent, guardian]
          description: Member's role within the family.
        joined_at:
          type: string
          format: date-time

          description: RFC 3339 timestamp of when the member joined.
      additionalProperties: true
    Child:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this resource.
        family_id:
          type: string
          format: uuid
          description: Identifier of the family this resource belongs to.
        name:
          type: string
          description: Human-readable display name.
        birth_date:
          type: string
          format: date-time
          description: Child's date of birth (YYYY-MM-DD). Used to derive the age-appropriate policy.
        avatar_url:
          type: string
          description: URL of the profile image.
        created_at:
          type: string
          format: date-time

          description: RFC 3339 timestamp of when the resource was created.
      additionalProperties: true
    ChildPolicy:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this resource.
        child_id:
          type: string
          format: uuid
          description: Identifier of the child this resource belongs to.
        name:
          type: string
          description: Human-readable display name.
        status:
          type: string
          enum: [active, paused, draft]
          description: Current lifecycle state of the resource.
        priority:
          type: integer
          description: Ordering weight when multiple policies apply; higher wins.
        version:
          type: integer
          description: Monotonically increasing version, bumped on every change.
        created_at:
          type: string
          format: date-time

          description: RFC 3339 timestamp of when the resource was created.
      additionalProperties: true
    PolicyRule:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this resource.
        policy_id:
          type: string
          format: uuid
          description: Identifier of the policy this resource belongs to.
        category:
          $ref: "#/components/schemas/RuleCategory"
        enabled:
          type: boolean
          description: Whether this item is currently active.
        config:
          type: object
          description: Category-specific configuration object for this rule.
          additionalProperties: true
        created_at:
          type: string
          format: date-time

          description: RFC 3339 timestamp of when the resource was created.
      additionalProperties: true
    RuleCategory:
      type: string
      description: One of the 123 OCSS rule categories (accepted and signed by the census; capability varies — see the Rule Categories reference).
      enum:
      - rating_age_gate
      - age_gate
      - adult_site_av_required
      - app_store_age_attestation
      - os_age_signal_ingest
      - age_signal_broadcast
      - hard_id_verification_escalation
      - social_media_min_age
      - ai_chatbot_age_assertion
      - teen_minimum_age_16_gate
      - content_rating
      - violence_threshold
      - sexual_content_threshold
      - profanity_threshold
      - commercial_pressure_threshold
      - substance_content_threshold
      - recognized_seal_allowlist
      - social_chat_control
      - web_filter_level
      - privacy_rating_tier_gate
      - privacy_rating_score_threshold
      - privacy_rating_dimension_gate
      - privacy_seal_required
      - unrated_privacy_default
      - ai_chatbot_tier_gate
      - ai_product_classification_gate
      - ai_safety_rating_threshold
      - ai_data_responsibility_rating_threshold
      - ai_transparency_rating_threshold
      - ai_minor_interaction
      - streaming_age_rating_enforce
      - ugc_world_age_gate
      - time_daily_limit
      - time_scheduled_hours
      - time_per_app_limit
      - time_downtime
      - phone_free_school_hours
      - purchase_approval
      - purchase_spending_cap
      - social_contacts
      - social_multiplayer
      - stranger_outreach_friction
      - privacy_account_creation
      - parental_consent_gate
      - notification_curfew
      - dumbphone_migration_mode
      - content_block_title
      - content_allow_title
      - content_allowlist_mode
      - content_descriptor_block
      - firearm_content_block
      - not_for_minors_block
      - purchase_block_iap
      - stranger_dm_block
      - web_safesearch
      - web_category_block
      - web_custom_allowlist
      - web_custom_blocklist
      - dm_restriction
      - ai_no_simulated_companionship
      - ai_no_unsupervised_therapy
      - ai_no_self_harm_promotion
      - ai_no_csam_generation
      - ai_no_personhood_deception
      - ai_companion_block
      - library_filter_compliance
      - gambling_mechanics_block
      - addictive_pattern_block
      - app_install_block
      - voice_chat_minor_limit
      - educational_credit
      - monitoring_activity
      - monitoring_alerts
      - social_account_content_monitoring
      - message_content_monitoring
      - image_content_monitoring
      - search_query_monitoring
      - flagged_content_digest
      - usage_timer_notification
      - parental_event_notification
      - screen_time_report
      - social_media_warning_label_render
      - digital_literacy_curriculum_link
      - school_alert_routing
      - privacy_location
      - privacy_profile_visibility
      - privacy_data_sharing
      - data_minimization_enforce
      - third_party_tracker_block
      - geolocation_precision_cap
      - targeted_ad_block
      - data_deletion_request
      - geolocation_opt_in
      - ai_training_data_opt_out
      - image_rights_minor
      - student_privacy_school_mode
      - commercial_data_ban
      - prosocial_weight
      - role_model_weight
      - representation_weight
      - dpia_request_generator
      - algo_feed_control
      - addictive_design_control
      - infinite_scroll_block
      - autoplay_disable
      - ai_no_engagement_dark_patterns
      - ai_synthetic_media_disclosure
      - algorithmic_audit
      - cipa_filter_attestation
      - minor_data_sale_audit_log
      - parental_attestation_cert
      - ai_toy_safety_cert
      - csam_reporting
      - esafety_commission_reporting
      - platform_liability_evidence_capture
      - age_appropriate_profile_mode
      - ncii_takedown
      - ephemeral_message_disable
      - dm_feature_disable
      - named_contact_block
      - crisis_resource_surface
      - presence_status_hide
      - harm_report_intake

    ProviderConnectConfig:
      type: object
      required: [authorize_url, token_url, profiles_url]
      properties:
        authorize_url:
          type: string
          format: uri
          description: The provider's OAuth 2.0 authorization endpoint (PKCE S256).
        token_url:
          type: string
          format: uri
          description: The provider's OAuth 2.0 token endpoint.
        profiles_url:
          type: string
          format: uri
          description: The provider's child-profile listing endpoint (bearer access token; the token is discarded after the profile fetch).
        scopes:
          type: array
          items:
            type: string
          description: OAuth scopes the connect ceremony requests, e.g. `child_profiles.read`.

      additionalProperties: true
    Platform:
      type: object
      properties:
        id:
          type: string
          description: Unique identifier for this resource.
        name:
          type: string
          description: Human-readable display name.
        category:
          type: string
          enum: [dns, streaming, gaming, device, browser]
          description: OCSS rule category this rule enforces (see the Rule Categories reference).
        tier:
          type: string
          enum: [compliant, provisional, pending]
          description: Accreditation / integration tier of the platform.
        description:
          type: string
          description: Human-readable description.
        icon_url:
          type: string
          description: URL of the platform or resource icon.
        auth_type:
          type: string
          enum: [api_key, oauth2, manual]
          description: How the platform is authenticated (e.g. `oauth`, `manual`).
        enabled:
          type: boolean
          description: Whether this item is currently active.
        enforcement_mode:
          type: string
          enum: [dns, device, api_write, manual_attested, coming_soon]
          description: >
            How Phosra enforces rules on this platform.
            `dns` = live DNS-provider API write (NextDNS/CleanBrowsing/Control D);
            `device` = on-device enforcement, requires Phosra app installed;
            `api_write` = live third-party write API;
            `manual_attested` = no programmatic API — Phosra generates setup steps and records a signed parent attestation. Never render "Enforced" for this value;
            `coming_soon` = adapter not yet built.
        rule_support:
          type: object
          additionalProperties:
            type: string
            enum: [dns, device, guided, unsupported]
          description: >
            Per-category enforcement support across all 123 OCSS rule categories.
            `dns` = DNS provider can filter this category;
            `device` = on-device enforcement handles it;
            `guided` = manual_attested platform, parent applies with Phosra guidance;
            `unsupported` = not addressable on this platform.

      additionalProperties: true
    ComplianceLink:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this resource.
        family_id:
          type: string
          format: uuid
          description: Identifier of the family this resource belongs to.
        platform_id:
          type: string
          description: Identifier of the associated platform.
        status:
          type: string
          enum: [verified, unverified, error]
          description: Current lifecycle state of the resource.
        external_id:
          type: string
          description: Identifier of the associated external.
        last_enforcement_at:
          type:
          - string
          - 'null'
          format: date-time
          description: RFC 3339 timestamp.
        last_enforcement_status:
          type: string
          description: Outcome of the most recent enforcement run against this link (e.g. `success`, `partial`, `error`).
        verified_at:
          type: string
          format: date-time

          description: RFC 3339 timestamp.
      additionalProperties: true
    EnforcementJob:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this resource.
        child_id:
          type: string
          format: uuid
          description: Identifier of the child this resource belongs to.
        policy_id:
          type: string
          format: uuid
          description: Identifier of the policy this resource belongs to.
        trigger_type:
          type: string
          enum: [manual, auto, webhook]
          description: What initiated the job (e.g. `manual`, `scheduled`, `policy_change`).
        status:
          type: string
          enum: [pending, running, completed, failed, partial]
          description: Current lifecycle state of the resource.
        started_at:
          type:
          - string
          - 'null'
          format: date-time
          description: RFC 3339 timestamp of when the job started running.
        completed_at:
          type:
          - string
          - 'null'
          format: date-time
          description: RFC 3339 timestamp of when the job finished.
        created_at:
          type: string
          format: date-time

          description: RFC 3339 timestamp of when the resource was created.
      additionalProperties: true
    EnforcementResult:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this resource.
        enforcement_job_id:
          type: string
          format: uuid
          description: Unique identifier (UUID) for the related resource.
        compliance_link_id:
          type: string
          format: uuid
          description: Unique identifier (UUID) for the related resource.
        platform_id:
          type: string
          description: Identifier of the associated platform.
        status:
          type: string
          enum: [pending, running, completed, failed, partial]
          description: Current lifecycle state of the resource.
        rules_applied:
          type: integer
          description: Number of rules successfully applied to the target during this run.
        rules_skipped:
          type: integer
          description: Number of rules skipped because the target does not support that category.
        rules_failed:
          type: integer
          description: Number of rules that returned an error while being applied.
        error_message:
          type:
          - string
          - 'null'
          description: Human-readable error detail when the operation failed; null on success.
        details:
          type: object
          description: Free-form provider response payload captured for debugging.
          additionalProperties: true
        started_at:
          type:
          - string
          - 'null'
          format: date-time
          description: RFC 3339 timestamp of when the job started running.
        completed_at:
          type:
          - string
          - 'null'
          format: date-time
          description: RFC 3339 timestamp of when the job finished.
      additionalProperties: true
    ProviderConnection:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this resource.
        family_id:
          type: string
          format: uuid
          description: Identifier of the family this resource belongs to.
        provider_id:
          type: string
          description: Identifier of the associated provider.
        status:
          type: string
          enum: [connected, disconnected, error]
          description: Current lifecycle state of the resource.
        last_sync_at:
          type: string
          format: date-time
          description: RFC 3339 timestamp.
        last_sync_status:
          type: string
          description: Outcome of the most recent sync (e.g. `success`, `error`).
        connected_at:
          type: string
          format: date-time

          description: RFC 3339 timestamp.
      additionalProperties: true
    SyncJob:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this resource.
        child_id:
          type: string
          format: uuid
          description: Identifier of the child this resource belongs to.
        policy_id:
          type: string
          format: uuid
          description: Identifier of the policy this resource belongs to.
        trigger_type:
          type: string
          enum: [manual, auto, webhook]
          description: What initiated the job (e.g. `manual`, `scheduled`, `policy_change`).
        status:
          type: string
          enum: [pending, running, completed, failed, partial]
          description: Current lifecycle state of the resource.
        started_at:
          type: string
          format: date-time
          description: RFC 3339 timestamp of when the job started running.
        completed_at:
          type: string
          format: date-time
          description: RFC 3339 timestamp of when the job finished.
        created_at:
          type: string
          format: date-time

          description: RFC 3339 timestamp of when the resource was created.
      additionalProperties: true
    SyncJobResult:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this resource.
        sync_job_id:
          type: string
          format: uuid
          description: Unique identifier (UUID) for the related resource.
        provider_id:
          type: string
          description: Identifier of the associated provider.
        status:
          type: string
          description: Current lifecycle state of the resource.
        rules_applied:
          type: integer
          description: Number of rules successfully applied to the target during this run.
        rules_skipped:
          type: integer
          description: Number of rules skipped because the target does not support that category.
        rules_failed:
          type: integer
          description: Number of rules that returned an error while being applied.
        error_message:
          type: string
          description: Human-readable error detail when the operation failed; null on success.
        details:
          type: object

          description: Free-form provider response payload captured for debugging.
          additionalProperties: true
      additionalProperties: true
    RatingSystem:
      type: object
      properties:
        id:
          type: string
          description: Unique identifier for this resource.
        name:
          type: string
          description: Human-readable display name.
        country:
          type: string
          description: ISO country associated with this resource.
        media_type:
          type: string
          description: Type of media the rating system applies to (e.g. `game`, `film`, `general`).
        description:
          type: string

          description: Human-readable description.
      additionalProperties: true
    Rating:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this resource.
        system_id:
          type: string
          description: Identifier of the associated system.
        code:
          type: string
          description: Short code for this rating tier or descriptor.
        name:
          type: string
          description: Human-readable display name.
        description:
          type: string
          description: Human-readable description.
        min_age:
          type: integer
          description: Minimum age (in years) this tier is appropriate for.
        display_order:
          type: integer
          description: Position of this rating within its system, lowest first.
        restrictive_idx:
          type: integer

          description: Relative strictness rank within the system; higher means more restrictive.
      additionalProperties: true
    ContentDescriptor:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this resource.
        system_id:
          type: string
          description: Identifier of the associated system.
        code:
          type: string
          description: Short code for this rating tier or descriptor.
        name:
          type: string
          description: Human-readable display name.
        category:
          type: string

          description: OCSS rule category this rule enforces (see the Rule Categories reference).
      additionalProperties: true
    RatingEquivalence:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this resource.
        rating_a:
          type: string
          format: uuid
          description: Unique identifier (UUID) for the related resource.
        rating_b:
          type: string
          format: uuid
          description: Unique identifier (UUID) for the related resource.
        strength:
          type: number
          description: "0-1, how equivalent the ratings are"

      additionalProperties: true
    AgeRatings:
      type: object
      properties:
        age:
          type: integer
          description: Child's age in years, derived from birth_date.
        ratings:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/Rating"

          description: Map of rating-system slug to the age-appropriate Rating in that system.
      additionalProperties: true
    Webhook:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this resource.
        family_id:
          type: string
          format: uuid
          description: Identifier of the family this resource belongs to.
        url:
          type: string
          description: HTTPS endpoint that receives POSTed webhook events.
        events:
          type: array
          items:
            type: string
          description: List of event types this webhook subscribes to.
        active:
          type: boolean
          description: Whether the webhook is currently active and receiving deliveries.
        created_at:
          type: string
          format: date-time
          description: RFC 3339 timestamp of when the resource was created.
        updated_at:
          type: string
          format: date-time

          description: RFC 3339 timestamp of the resource's most recent update.
      additionalProperties: true
    WebhookDelivery:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this resource.
        webhook_id:
          type: string
          format: uuid
          description: Identifier of the webhook.
        event:
          type: string
          description: Event type that triggered this delivery (e.g. `policy.updated`).
        payload:
          type: object
          description: The event payload delivered to the endpoint, matching the webhook event schema.
          additionalProperties: true
        response_code:
          type:
          - integer
          - 'null'
          description: HTTP status code returned by the subscriber endpoint; null if unreachable.
        success:
          type: boolean
          description: Whether the delivery received a 2xx response from the subscriber.
        attempts:
          type: integer
          description: Number of delivery attempts made so far, including retries.
        next_retry_at:
          type:
          - string
          - 'null'
          format: date-time
          description: RFC 3339 timestamp.
        created_at:
          type: string
          format: date-time

          description: RFC 3339 timestamp of when the resource was created.
      additionalProperties: true
    Standard:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this resource.
        slug:
          type: string
          description: URL-safe stable identifier.
        name:
          type: string
          description: Human-readable display name.
        organization:
          type: string
          description: Name of the organization that publishes this standard.
        description:
          type: string
          description: Human-readable description.
        long_description:
          type: string
          description: Extended multi-paragraph description shown on the standard detail page.
        icon_url:
          type: string
          description: URL of the platform or resource icon.
        version:
          type: string
          description: Monotonically increasing version, bumped on every change.
        published:
          type: boolean
          description: Whether this standard is published and adoptable by families.
        min_age:
          type:
          - integer
          - 'null'
          description: Minimum age (in years) this tier is appropriate for.
        max_age:
          type:
          - integer
          - 'null'
          description: Upper age bound (in years) this standard is intended for; null if open-ended.
        adoption_count:
          type: integer
          description: Number of families that have adopted this standard.
        rules:
          type: array
          items:
            $ref: "#/components/schemas/StandardRule"
          description: List of rules.
        created_at:
          type: string
          format: date-time
          description: RFC 3339 timestamp of when the resource was created.
        updated_at:
          type: string
          format: date-time

          description: RFC 3339 timestamp of the resource's most recent update.
      additionalProperties: true
    StandardRule:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this resource.
        standard_id:
          type: string
          format: uuid
          description: Unique identifier (UUID) for the related resource.
        category:
          $ref: "#/components/schemas/RuleCategory"
        label:
          type: string
          description: Human-readable label describing what this rule enforces.
        enabled:
          type: boolean
          description: Whether this item is currently active.
        config:
          type: object
          description: Category-specific configuration object for this rule.
          additionalProperties: true
        sort_order:
          type: integer

          description: Position of this rule within the standard, lowest first.
      additionalProperties: true
    StandardAdoption:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this resource.
        child_id:
          type: string
          format: uuid
          description: Identifier of the child this resource belongs to.
        standard_id:
          type: string
          format: uuid
          description: Unique identifier (UUID) for the related resource.
        adopted_at:
          type: string
          format: date-time

          description: RFC 3339 timestamp.
      additionalProperties: true
    UIFeedback:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this resource.
        page_route:
          type: string
          description: App route where the feedback was captured (e.g. `/dashboard/children`).
        css_selector:
          type: string
          description: CSS selector of the element the feedback is anchored to.
        component_hint:
          type:
          - string
          - 'null'
          description: Best-guess component name for the anchored element; null if unknown.
        comment:
          type: string
          description: Reviewer's free-text feedback comment.
        reviewer_name:
          type: string
          description: Display name of the reviewer who left the feedback.
        status:
          type: string
          enum: [open, approved, dismissed, fixed]
          description: Current lifecycle state of the resource.
        viewport_width:
          type:
          - integer
          - 'null'
          description: Browser viewport width in pixels when the feedback was captured.
        viewport_height:
          type:
          - integer
          - 'null'
          description: Browser viewport height in pixels when the feedback was captured.
        click_x:
          type:
          - integer
          - 'null'
          description: X coordinate (px) of the feedback click, relative to the viewport.
        click_y:
          type:
          - integer
          - 'null'
          description: Y coordinate (px) of the feedback click, relative to the viewport.
        created_at:
          type: string
          format: date-time
          description: RFC 3339 timestamp of when the resource was created.
        resolved_at:
          type:
          - string
          - 'null'
          format: date-time
          description: RFC 3339 timestamp.
      additionalProperties: true
    QuickSetupRequest:
      type: object
      required: [child_name, birth_date]
      properties:
        family_id:
          type: string
          format: uuid
          description: Optional existing family UUID to add the child to
        family_name:
          type: string
          description: "Optional name for a new family (defaults to '{child_name}'s Family')"
        child_name:
          type: string
          description: The child's display name
        birth_date:
          type: string
          format: date
          description: Child's birth date in YYYY-MM-DD format
        strictness:
          type: string
          enum: [recommended, strict, relaxed]
          description: "Strictness preset (default: recommended)"

      additionalProperties: true
    QuickSetupResponse:
      type: object
      properties:
        family:
          $ref: "#/components/schemas/Family"
        child:
          $ref: "#/components/schemas/Child"
        policy:
          $ref: "#/components/schemas/ChildPolicy"
        rules:
          type: array
          items:
            $ref: "#/components/schemas/PolicyRule"
          description: List of rules.
        age_group:
          type: string
          description: Derived age band used to pick default rules (e.g. `elementary`, `teen`).
        max_ratings:
          type: object
          additionalProperties:
            type: string
          description: Map of rating-system slug to the maximum allowed rating code.
        rule_summary:
          $ref: "#/components/schemas/RuleSummary"

      additionalProperties: true
    RuleSummary:
      type: object
      properties:
        screen_time_minutes:
          type: integer
          description: Daily screen-time limit in minutes.
        bedtime_hour:
          type: integer
          description: Hour of day (0-23, local time) when the bedtime downtime window begins.
        web_filter_level:
          type: string
          description: Web-filtering strictness level (e.g. `strict`, `moderate`, `light`).
        content_rating:
          type: string
          description: Maximum content rating allowed (e.g. `PG`, `TV-14`).
        total_rules_enabled:
          type: integer

          description: Total number of rules enabled by this quick-setup preset.
      additionalProperties: true
    FamilyOverview:
      type: object
      properties:
        children:
          type: array
          items:
            type: object
            properties:
              child:
                $ref: "#/components/schemas/Child"
              active_policies:
                type: integer
                description: Number of currently active policies for this child.
              last_enforcement_at:
                type:
                - string
                - 'null'
                format: date-time
                description: RFC 3339 timestamp.
              enforcement_status:
                type: string
                description: Aggregate enforcement health for this child (e.g. `healthy`, `warning`, `error`).
            additionalProperties: true
          description: List of children.
        total_platforms:
          type: integer
          description: Total number of platforms enforcement is applied across.
        enforcement_health:
          type: string
          enum: [healthy, warning, error]
          description: 'One of: healthy, warning, error.'
        recent_enforcements:
          type: array
          items:
            $ref: "#/components/schemas/EnforcementJob"

          description: List of recent enforcements.
      additionalProperties: true
    DeviceRegistration:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this resource.
        child_id:
          type: string
          format: uuid
          description: Identifier of the child this resource belongs to.
        family_id:
          type: string
          format: uuid
          description: Identifier of the family this resource belongs to.
        platform_id:
          type: string
          description: Identifier of the associated platform.
        device_name:
          type: string
          description: User-assigned device name (e.g. "Ada's iPhone").
        device_model:
          type: string
          description: Hardware model identifier (e.g. `iPhone15,3`).
        os_version:
          type: string
          description: Operating-system version reported by the device (e.g. `18.5`).
        app_version:
          type: string
          description: Version of the Phosra app installed on the device.
        apns_token:
          type:
          - string
          - 'null'
          description: Apple Push Notification service device token; null until the device registers for push.
        capabilities:
          type: array
          items:
            type: string
          description: "Apple frameworks the device supports (e.g. FamilyControls, ManagedSettings, DeviceActivity)"
        enforcement_summary:
          type: object
          description: "Per-category enforcement results from the device's last enforcement_status report"
          additionalProperties: true
        last_seen_at:
          type:
          - string
          - 'null'
          format: date-time
          description: RFC 3339 timestamp.
        last_policy_version:
          type: integer
          description: Policy version the device last successfully applied.
        status:
          type: string
          enum: [active, inactive, revoked]
          description: Current lifecycle state of the resource.
        created_at:
          type: string
          format: date-time
          description: RFC 3339 timestamp of when the resource was created.
        updated_at:
          type: string
          format: date-time

          description: RFC 3339 timestamp of the resource's most recent update.
      additionalProperties: true
    RegisterDeviceRequest:
      type: object
      required: [device_name, device_model, os_version, app_version]
      properties:
        device_name:
          type: string
          description: User-assigned device name (e.g. "Ada's iPhone").
        device_model:
          type: string
          description: Hardware model identifier (e.g. `iPhone15,3`).
        os_version:
          type: string
          description: Operating-system version reported by the device (e.g. `18.5`).
        app_version:
          type: string
          description: Version of the Phosra app installed on the device.
        apns_token:
          type: string
          description: Apple Push Notification service device token; null until the device registers for push.
        capabilities:
          type: array
          items:
            type: string
          description: "Apple frameworks the device supports (e.g. FamilyControls, ManagedSettings, DeviceActivity)"

      additionalProperties: true
    RegisterDeviceResponse:
      type: object
      properties:
        device:
          $ref: "#/components/schemas/DeviceRegistration"
        api_key:
          type: string
          description: One-time device API key (store in Keychain)

      additionalProperties: true
    UpdateDeviceRequest:
      type: object
      properties:
        device_name:
          type: string
          description: User-assigned device name (e.g. "Ada's iPhone").
        apns_token:
          type: string
          description: Apple Push Notification service device token; null until the device registers for push.
        app_version:
          type: string
          description: Version of the Phosra app installed on the device.
        os_version:
          type: string

          description: Operating-system version reported by the device (e.g. `18.5`).
      additionalProperties: true
    CompiledPolicy:
      type: object
      properties:
        version:
          type: integer
          description: Monotonically increasing version, bumped on every change.
        child_id:
          type: string
          format: uuid
          description: Identifier of the child this resource belongs to.
        child_age:
          type: integer
          description: Child's age in years, derived from birth_date.
        age_group:
          type: string
          description: Derived age band used to pick default rules (e.g. `elementary`, `teen`).
        policy_id:
          type: string
          format: uuid
          description: Identifier of the policy this resource belongs to.
        status:
          type: string
          description: Current lifecycle state of the resource.
        generated_at:
          type: string
          format: date-time
          description: RFC 3339 timestamp.
        content_filter:
          type: object
          properties:
            age_rating:
              type: string
              description: Maximum age rating permitted for content on this child's devices.
            max_ratings:
              type: object
              additionalProperties:
                type: string
              description: Map of rating-system slug to the maximum allowed rating code.
            blocked_apps:
              type: array
              items:
                type: string
              description: List of blocked apps.
            allowed_apps:
              type: array
              items:
                type: string
              description: List of allowed apps.
            allowlist_mode:
              type: boolean
              description: When true, only apps on the allowlist are permitted; everything else is blocked.
          description: Resolved content-filtering settings for the child.
          additionalProperties: true
        screen_time:
          type: object
          properties:
            daily_limit_minutes:
              type: integer
              description: Total daily screen-time budget across all apps, in minutes.
            per_app_limits:
              type: array
              items:
                type: object
                properties:
                  bundle_id:
                    type: string
                    description: Identifier of the associated bundle.
                  daily_minutes:
                    type: integer
                    description: Daily time budget for this app, in minutes.
                additionalProperties: true
              description: List of per app limits.
            downtime_windows:
              type: array
              items:
                type: object
                properties:
                  days_of_week:
                    type: array
                    items:
                      type: string
                    description: List of days of week.
                  start_time:
                    type: string
                    description: Start of the window in 24-hour `HH:MM` local time.
                  end_time:
                    type: string
                    description: End of the window in 24-hour `HH:MM` local time.
                additionalProperties: true
              description: List of downtime windows.
            always_allowed_apps:
              type: array
              items:
                type: string
              description: List of always allowed apps.
            schedule:
              type: object
              properties:
                weekday:
                  type: object
                  properties:
                    start:
                      type: string
                      description: Window start in 24-hour `HH:MM` local time.
                    end:
                      type: string
                      description: Window end in 24-hour `HH:MM` local time.
                  description: Screen-time window applied Monday through Friday.
                  additionalProperties: true
                weekend:
                  type: object
                  properties:
                    start:
                      type: string
                      description: Window start in 24-hour `HH:MM` local time.
                    end:
                      type: string
                      description: Window end in 24-hour `HH:MM` local time.
                  description: Screen-time window applied Saturday and Sunday.
                  additionalProperties: true
              description: Weekday/weekend screen-time schedule.
              additionalProperties: true
          description: Resolved screen-time limits and schedule for the child.
          additionalProperties: true
        purchases:
          type: object
          properties:
            require_approval:
              type: boolean
              description: Whether a parent must approve each purchase before it completes.
            block_iap:
              type: boolean
              description: Whether in-app purchases are blocked entirely.
            spending_cap_usd:
              type: number
              description: Maximum spend allowed per period, in US dollars.
          description: Resolved purchase-control settings for the child.
          additionalProperties: true
        privacy:
          type: object
          properties:
            location_sharing_enabled:
              type: boolean
              description: Whether the child may share device location with contacts.
            profile_visibility:
              type: string
              description: Who can see the child's profile (e.g. `private`, `friends_only`, `public`).
            account_creation_approval:
              type: boolean
              description: Whether creating new third-party accounts requires parent approval.
            data_sharing_restricted:
              type: boolean
              description: Whether third-party data sharing is restricted for this child.
          description: Resolved privacy settings for the child.
          additionalProperties: true
        social:
          type: object
          properties:
            chat_mode:
              type: string
              description: Chat permission level (e.g. `off`, `restricted`, `open`).
            dm_restriction:
              type: string
              description: Direct-message restriction level (e.g. `contacts_only`, `blocked`).
            multiplayer_mode:
              type: string
              description: Multiplayer permission level (e.g. `off`, `friends_only`, `open`).
          description: Resolved social and chat settings for the child.
          additionalProperties: true
        notifications:
          type: object
          properties:
            curfew_start:
              type: string
              description: Start of the nightly notification curfew in `HH:MM` local time.
            curfew_end:
              type: string
              description: End of the nightly notification curfew in `HH:MM` local time.
            usage_timer_minutes:
              type: integer
              description: Interval in minutes between on-device usage-timer notifications.
          description: Resolved notification-curfew settings for the child.
          additionalProperties: true
        web_filter:
          type: object
          properties:
            level:
              type: string
              description: Web-filter strictness level (e.g. `strict`, `moderate`, `light`).
            safe_search:
              type: boolean
              description: Whether SafeSearch is forced on supported search engines.
            blocked_domains:
              type: array
              items:
                type: string
              description: List of blocked domains.
            allowed_domains:
              type: array
              items:
                type: string
              description: List of allowed domains.
            blocked_categories:
              type: array
              items:
                type: string

              description: List of blocked categories.
          description: Resolved web-filtering settings for the child.
          additionalProperties: true
      additionalProperties: true
    DeviceReportRequest:
      type: object
      required: [report_type, payload, reported_at]
      properties:
        report_type:
          type: string
          enum: [screen_time, app_usage, web_activity, blocked_attempt, enforcement_status]
          description: 'One of: screen_time, app_usage, web_activity, blocked_attempt, enforcement_status.'
        payload:
          type: object
          description: "Report data (varies by report_type). For enforcement_status see EnforcementStatusPayload."
          additionalProperties: true
        reported_at:
          type: string
          format: date-time

          description: RFC 3339 timestamp.
      additionalProperties: true
    EnforcementStatusPayload:
      type: object
      description: "Payload for report_type: enforcement_status -- per-category enforcement results"
      properties:
        policy_version:
          type: integer
          description: "The policy version these results correspond to"
        results:
          type: array
          items:
            $ref: "#/components/schemas/CategoryEnforcementResult"

          description: List of results.
      additionalProperties: true
    CategoryEnforcementResult:
      type: object
      properties:
        category:
          type: string
          description: "Phosra rule category (e.g. content_rating, time_daily_limit)"
        status:
          type: string
          enum: [enforced, partial, failed, unsupported]
          description: "Enforcement outcome on this device"
        framework:
          type: string
          description: "Apple framework used (ManagedSettings, DeviceActivity, FamilyControls, none)"
        detail:
          type: string
          description: "Optional error message or implementation note"

      additionalProperties: true
    CategoryFrameworkMapping:
      type: object
      description: "Maps a Phosra rule category to the Apple framework and API class needed to enforce it"
      properties:
        framework:
          type: string
          description: "Apple framework: ManagedSettings, DeviceActivity, FamilyControls, or none"
        api_class:
          type: string
          description: "Specific API class (e.g. ManagedSettingsStore.webContent, DeviceActivitySchedule)"
        min_os:
          type: string
          description: "Minimum iOS version required (e.g. 16.0)"
        notes:
          type: string
          description: "Implementation hints for the iOS app"

      additionalProperties: true
    APNsPushPayload:
      type: object
      description: "Silent push notification sent to iOS devices when a child's policy is updated. Delivered via APNs HTTP/2 with push-type=background and priority=5."
      properties:
        aps:
          type: object
          properties:
            content-available:
              type: integer
              description: "Always 1 -- marks as silent/background push"
              enum: [1]
          description: Standard APNs `aps` dictionary for the silent push.
          additionalProperties: true
        phosra:
          type: object
          properties:
            event:
              type: string
              description: "Event type -- currently always policy.updated"
              enum: ["policy.updated"]
            version:
              type: integer
              description: "New policy version number. Compare with cached version to decide whether to call GET /device/policy."
          description: Phosra-specific payload carried alongside the silent push.
          additionalProperties: true
      example:
        aps:
          content-available: 1
        phosra:
          event: "policy.updated"
          version: 3
      additionalProperties: true
    ApplePlatformMappings:
      type: object
      properties:
        age_ratings:
          type: object
          additionalProperties:
            type: string
          description: Map of Phosra rating tier to the platform's native rating value.
        app_categories:
          type: object
          additionalProperties:
            type: object
            properties:
              bundle_ids:
                type: array
                items:
                  type: string
              app_store_category:
                type: string
            additionalProperties: true
          description: Map of Phosra app category to the platform's bundle/package identifiers.
        system_apps:
          type: object
          additionalProperties:
            type: string
          description: Map of Phosra system-app key to the platform's native identifier.
        always_allowed:
          type: array
          items:
            type: string
          description: List of always allowed.
        category_frameworks:
          type: object
          description: "Phosra rule category -> Apple framework mapping for on-device enforcement"
          additionalProperties:
            $ref: "#/components/schemas/CategoryFrameworkMapping"

      additionalProperties: true
    AndroidCategoryFrameworkMapping:
      type: object
      description: "Maps a Phosra rule category to the Android framework and API class needed to enforce it"
      properties:
        framework:
          type: string
          description: "Android framework: DevicePolicyManager, UsageStatsManager, VpnService, AccessibilityService, NotificationListenerService, PackageManager, ContentResolver, AlarmManager, NotificationManager, or none"
        api_class:
          type: string
          description: "Specific API class or method (e.g. setPackagesSuspended, queryUsageStats, onAccessibilityEvent)"
        min_sdk:
          type: integer
          description: "Minimum Android SDK version required (e.g. 26 for Android 8.0, 28 for Android 9.0)"
        notes:
          type: string
          description: "Implementation hints for the Android app"

      additionalProperties: true
    AndroidPlatformMappings:
      type: object
      properties:
        age_ratings:
          type: object
          additionalProperties:
            type: string
          description: Map of Phosra rating tier to the platform's native rating value.
        app_categories:
          type: object
          additionalProperties:
            type: object
            properties:
              package_names:
                type: array
                items:
                  type: string
              play_store_category:
                type: string
            additionalProperties: true
          description: Map of Phosra app category to the platform's bundle/package identifiers.
        system_apps:
          type: object
          additionalProperties:
            type: string
          description: Map of Phosra system-app key to the platform's native identifier.
        always_allowed:
          type: array
          items:
            type: string
          description: List of always allowed.
        category_frameworks:
          type: object
          description: "Phosra rule category -> Android framework mapping for on-device enforcement"
          additionalProperties:
            $ref: "#/components/schemas/AndroidCategoryFrameworkMapping"

    # ── Sources ──────────────────────────────────────────────────────

      additionalProperties: true
    Source:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this resource.
        child_id:
          type: string
          format: uuid
          description: Identifier of the child this resource belongs to.
        family_id:
          type: string
          format: uuid
          description: Identifier of the family this resource belongs to.
        source_slug:
          type: string
          description: Stable slug identifying the source platform (e.g. `nextdns`).
        display_name:
          type: string
          description: Human-readable name of the source platform.
        api_tier:
          type: string
          enum: [managed, guided]
          description: 'One of: managed, guided.'
        status:
          type: string
          enum: [pending, connected, syncing, error, disconnected]
          description: Current lifecycle state of the resource.
        auto_sync:
          type: boolean
          description: Whether policy changes are automatically pushed to this source.
        capabilities:
          type: array
          items:
            $ref: "#/components/schemas/SourceCapability"
          description: List of OCSS rule categories this platform can enforce.
        config:
          type: object
          description: Category-specific configuration object for this rule.
          additionalProperties: true
        last_sync_at:
          type:
          - string
          - 'null'
          format: date-time
          description: RFC 3339 timestamp.
        last_sync_status:
          type:
          - string
          - 'null'
          description: Outcome of the most recent sync (e.g. `success`, `error`).
        sync_version:
          type: integer
          description: Monotonic counter incremented on each successful sync.
        error_message:
          type:
          - string
          - 'null'
          description: Human-readable error detail when the operation failed; null on success.
        created_at:
          type: string
          format: date-time
          description: RFC 3339 timestamp of when the resource was created.
        updated_at:
          type: string
          format: date-time

          description: RFC 3339 timestamp of the resource's most recent update.
      additionalProperties: true
    SourceCapability:
      type: object
      properties:
        category:
          $ref: "#/components/schemas/RuleCategory"
        support_level:
          type: string
          enum: [full, partial, none]
          description: 'One of: full, partial, none.'
        read_write:
          type: string
          enum: [push_only, pull_only, bidirectional]
          description: 'One of: push_only, pull_only, bidirectional.'
        notes:
          type: string

          description: Free-form implementation notes for this capability.
      additionalProperties: true
    SourceSyncJob:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this resource.
        source_id:
          type: string
          format: uuid
          description: Unique identifier (UUID) for the related resource.
        sync_mode:
          type: string
          enum: [full, incremental, single_rule]
          description: 'One of: full, incremental, single_rule.'
        trigger_type:
          type: string
          enum: [manual, auto, webhook, policy_change]
          description: What initiated the job (e.g. `manual`, `scheduled`, `policy_change`).
        status:
          type: string
          enum: [pending, running, completed, failed, partial]
          description: Current lifecycle state of the resource.
        rules_pushed:
          type: integer
          description: Number of rules pushed to the source during this sync.
        rules_skipped:
          type: integer
          description: Number of rules skipped because the target does not support that category.
        rules_failed:
          type: integer
          description: Number of rules that returned an error while being applied.
        error_message:
          type:
          - string
          - 'null'
          description: Human-readable error detail when the operation failed; null on success.
        started_at:
          type:
          - string
          - 'null'
          format: date-time
          description: RFC 3339 timestamp of when the job started running.
        completed_at:
          type:
          - string
          - 'null'
          format: date-time
          description: RFC 3339 timestamp of when the job finished.
        created_at:
          type: string
          format: date-time

          description: RFC 3339 timestamp of when the resource was created.
      additionalProperties: true
    SourceSyncResult:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique identifier for this resource.
        job_id:
          type: string
          format: uuid
          description: Unique identifier (UUID) for the related resource.
        source_id:
          type: string
          format: uuid
          description: Unique identifier (UUID) for the related resource.
        rule_category:
          $ref: "#/components/schemas/RuleCategory"
        status:
          type: string
          enum: [pushed, skipped, failed, unsupported]
          description: Current lifecycle state of the resource.
        source_value:
          type: object
          description: The value Phosra pushed to the source for this rule category.
          additionalProperties: true
        source_response:
          type: object
          description: Raw response the source returned when the rule was applied.
          additionalProperties: true
        error_message:
          type:
          - string
          - 'null'
          description: Human-readable error detail when the operation failed; null on success.
        created_at:
          type: string
          format: date-time

          description: RFC 3339 timestamp of when the resource was created.
      additionalProperties: true
    AvailableSource:
      type: object
      properties:
        slug:
          type: string
          description: URL-safe stable identifier.
        display_name:
          type: string
          description: Human-readable name of the source platform.
        api_tier:
          type: string
          enum: [managed, guided]
          description: 'One of: managed, guided.'
        auth_type:
          type: string
          description: How the platform is authenticated (e.g. `oauth`, `manual`).
        website:
          type: string
          description: Marketing or documentation URL for the source platform.
        description:
          type: string

          description: Human-readable description.
      additionalProperties: true
    GuidedStep:
      type: object
      properties:
        step_number:
          type: integer
          description: Ordinal position of this step in the guided setup flow, starting at 1.
        title:
          type: string
          description: Short title of the guided setup step.
        description:
          type: string
          description: Human-readable description.
        image_url:
          type: string
          description: URL for image.
        deep_link:
          type: string

          description: Optional deep link that opens the relevant screen in the source app.
      additionalProperties: true
  responses:
    BadRequest:
      description: The request was malformed — a required field is missing, a value failed validation, or an identifier is not a well-formed UUID.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: Bad Request
            message: child_name is required
            code: 400
    Unauthorized:
      description: Authentication failed. Send a Bearer WorkOS JWT, or a `phosra_live_`/`phosra_test_` developer API key, in the `Authorization` header.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: Unauthorized
            message: missing or invalid Authorization header
            code: 401
    Forbidden:
      description: Authenticated, but not permitted to act on this resource — for example, you are not a member of the target family.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: Forbidden
            message: not a member of this family
            code: 403
    NotFound:
      description: No resource matches the identifier supplied in the path.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: Not Found
            message: policy not found
            code: 404
    Conflict:
      description: The request conflicts with the current state of the resource — for example, a resource with the same natural key already exists.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: Conflict
            message: resource already exists
            code: 409
    RateLimited:
      description: Too many requests. Back off and retry after the interval given in the `Retry-After` response header.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: Too Many Requests
            message: rate limit exceeded
            code: 429
    ServerError:
      description: An unexpected error occurred inside Phosra. The request is safe to retry with exponential backoff; contact support if it persists.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: Internal Server Error
            message: internal error
            code: 500
    BadGateway:
      description: A downstream provider returned an invalid response while Phosra was fulfilling the request. Retry with backoff.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: Bad Gateway
            message: downstream provider error
            code: 502
    ServiceUnavailable:
      description: A downstream provider or dependency is temporarily unavailable. Retry with backoff.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: Service Unavailable
            message: downstream provider unavailable
            code: 503
paths:
  # ── Auth ──────────────────────────────────────────────────────────
  # NOTE: Authentication is handled entirely by WorkOS AuthKit. The Go API
  # only validates WorkOS RS256 JWTs (iss: api.workos.com/user_management/
  # client_01KH1YWWNH3MPMCPHCF6GNZ777). No /auth/register, /auth/login,
  # or /auth/refresh endpoints exist on this server. Developer API keys
  # (phosra_live_<64hex> / phosra_test_<64hex>) authenticate /developer/ routes.

  /auth/logout:
    post:
      operationId: logoutNoOpSessionRevocationIsManagedByWorkos
      summary: Logout (no-op — session revocation is managed by WorkOS)
      description: "Clears the client-side session. Session and token revocation are handled by WorkOS, so this endpoint always succeeds and makes no server-side state change."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          await phosra.auth.logout();
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X POST "https://phosra-api-sandbox-production.up.railway.app/api/v1/auth/logout" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.post(
              f"{BASE}/auth/logout",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("POST", base+"/auth/logout", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Auth]
      responses:
        "204":
          description: Logged out

        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /auth/me:
    get:
      operationId: getCurrentUser
      summary: Get current user
      description: "Returns the profile of the user associated with the current bearer token, including id, email, and display name. Use it to hydrate the signed-in state after login."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.auth.me();
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/auth/me" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/auth/me",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/auth/me", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Auth]
      responses:
        "200":
          description: Current user
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"

  # ── Quick Setup ───────────────────────────────────────────────────

              example:
                id: 9f8b2a14-6c3d-4e51-8a72-1f0b9c4d2e63
                email: parent@example.com
                name: Ada Rivera
                created_at: '2026-06-18T14:32:07Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /setup/quick:
    post:
      operationId: oneStepOnboarding
      summary: One-step onboarding
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.setup.quick({
            family_name: "The Chen Family",
            child_name: "Mia",
            birth_date: "2015-06-01",
            strictness: "balanced"
          });
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X POST "https://phosra-api-sandbox-production.up.railway.app/api/v1/setup/quick" \
            -H "Authorization: Bearer $PHOSRA_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
            "family_name": "The Chen Family",
            "child_name": "Mia",
            "birth_date": "2015-06-01",
            "strictness": "balanced"
          }'
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.post(
              f"{BASE}/setup/quick",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
              json={
                  "family_name": "The Chen Family",
                  "child_name": "Mia",
                  "birth_date": "2015-06-01",
                  "strictness": "balanced"
              },
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"bytes"
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	body := bytes.NewBufferString(`{
            "family_name": "The Chen Family",
            "child_name": "Mia",
            "birth_date": "2015-06-01",
            "strictness": "balanced"
          }`)
          	req, _ := http.NewRequest("POST", base+"/setup/quick", body)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	req.Header.Set("Content-Type", "application/json")
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: "Creates a family (or uses existing), adds a child, generates age-appropriate policy rules, and activates the policy."
      tags: [Quick Setup]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/QuickSetupRequest"
      responses:
        "201":
          description: Setup complete
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/QuickSetupResponse"
              example:
                family:
                  id: fcdf4277-ba14-4732-8db5-0369b2c88d8b
                  name: "Ada's Family"
                  created_at: '2026-06-18T14:32:07Z'
                child:
                  id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                  family_id: fcdf4277-ba14-4732-8db5-0369b2c88d8b
                  name: Ada
                  birth_date: '2015-04-10T00:00:00Z'
                  created_at: '2026-06-18T14:32:07Z'
                policy:
                  id: 7b3e9c04-2d1a-4f68-9e5b-8c07a1f34d29
                  child_id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                  name: "Ada's Protection Policy"
                  status: active
                  priority: 0
                  version: 1
                  created_at: '2026-06-18T14:32:07Z'
                age_group: elementary
                max_ratings:
                  esrb: E10+
                  mpaa: PG
                rule_summary:
                  screen_time_minutes: 120
                  bedtime_hour: 21
                  web_filter_level: moderate
                  content_rating: PG
                  total_rules_enabled: 14
        "400":
          description: Invalid request (missing child_name or birth_date)

  # ── Families ──────────────────────────────────────────────────────

        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /families:
    get:
      operationId: listUserSFamilies
      summary: List user's families
      description: "Returns every family the authenticated user belongs to, with the caller's role in each. Families are the top-level container for children and policies."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.families.list();
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/families" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/families",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/families", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Families]
      responses:
        "200":
          description: Family list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Family"
              example:
              - &id001
                id: fcdf4277-ba14-4732-8db5-0369b2c88d8b
                name: Ada's Family
                created_at: '2026-06-18T14:32:07Z'
                updated_at: '2026-06-30T09:15:44Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
    post:
      operationId: createAFamily
      summary: Create a family
      description: "Creates a new family owned by the authenticated user and returns the created resource. The creator is added as the first member with the `owner` role."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.families.create({
            name: "The Chen Family"
          });
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X POST "https://phosra-api-sandbox-production.up.railway.app/api/v1/families" \
            -H "Authorization: Bearer $PHOSRA_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
            "name": "The Chen Family"
          }'
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.post(
              f"{BASE}/families",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
              json={
                  "name": "The Chen Family"
              },
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"bytes"
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	body := bytes.NewBufferString(`{
            "name": "The Chen Family"
          }`)
          	req, _ := http.NewRequest("POST", base+"/families", body)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	req.Header.Set("Content-Type", "application/json")
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Families]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
      responses:
        "201":
          description: Family created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Family"

              example:
                id: fcdf4277-ba14-4732-8db5-0369b2c88d8b
                name: "Ada's Family"
                created_at: '2026-06-18T14:32:07Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /families/{familyID}:
    parameters:
    - name: familyID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      operationId: getFamily
      summary: Get family
      description: "Returns a single family by ID, including its members. The caller must be a member of the family."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.families.get("f0000000-0000-4000-8000-0000000000f1");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/families/f0000000-0000-4000-8000-0000000000f1" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/families/f0000000-0000-4000-8000-0000000000f1",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/families/f0000000-0000-4000-8000-0000000000f1", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Families]
      responses:
        "200":
          description: Family details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Family"
              example: *id001
        "404":
          description: Family not found
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
    put:
      operationId: updateFamily
      summary: Update family
      description: "Updates mutable fields (such as the display name) on a family and returns the updated resource. Requires the `owner` or `admin` role."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.families.update("f0000000-0000-4000-8000-0000000000f1", {
            name: "The Chen Household"
          });
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X PUT "https://phosra-api-sandbox-production.up.railway.app/api/v1/families/f0000000-0000-4000-8000-0000000000f1" \
            -H "Authorization: Bearer $PHOSRA_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
            "name": "The Chen Household"
          }'
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.put(
              f"{BASE}/families/f0000000-0000-4000-8000-0000000000f1",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
              json={
                  "name": "The Chen Household"
              },
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"bytes"
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	body := bytes.NewBufferString(`{
            "name": "The Chen Household"
          }`)
          	req, _ := http.NewRequest("PUT", base+"/families/f0000000-0000-4000-8000-0000000000f1", body)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	req.Header.Set("Content-Type", "application/json")
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Families]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Family"
              example:
                id: fcdf4277-ba14-4732-8db5-0369b2c88d8b
                name: "Ada's Family"
                created_at: '2026-06-18T14:32:07Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
    delete:
      operationId: deleteFamily
      summary: Delete family
      description: "Permanently deletes a family along with its children, policies, and rules. This cannot be undone and requires the `owner` role."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          await phosra.families.delete("f0000000-0000-4000-8000-0000000000f1");
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X DELETE "https://phosra-api-sandbox-production.up.railway.app/api/v1/families/f0000000-0000-4000-8000-0000000000f1" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.delete(
              f"{BASE}/families/f0000000-0000-4000-8000-0000000000f1",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("DELETE", base+"/families/f0000000-0000-4000-8000-0000000000f1", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Families]
      responses:
        "204":
          description: Deleted

  # ── Family Members ────────────────────────────────────────────────

        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /families/{familyID}/members:
    parameters:
    - name: familyID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      operationId: listFamilyMembers
      summary: List family members
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.members.list("f0000000-0000-4000-8000-0000000000f1");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/families/f0000000-0000-4000-8000-0000000000f1/members" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/families/f0000000-0000-4000-8000-0000000000f1/members",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/families/f0000000-0000-4000-8000-0000000000f1/members", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: List all members of a family with their roles (owner, parent, guardian).
      tags: [Families]
      responses:
        "200":
          description: Member list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/FamilyMember"
              example:
              - id: aa03f1c7-5d29-4b60-8e14-2f7a9c0d6b85
                family_id: fcdf4277-ba14-4732-8db5-0369b2c88d8b
                user_id: 9f8b2a14-6c3d-4e51-8a72-1f0b9c4d2e63
                role: owner
                joined_at: '2026-06-18T14:33:10Z'
        "403":
          description: Not a member of this family
        "404":
          description: Family not found
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
    post:
      operationId: addAMemberToAFamily
      summary: Add a member to a family
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.members.invite("f0000000-0000-4000-8000-0000000000f1", {
            user_id: "3fa85f64-5717-4562-b3fc-2c963f66afa6",
            role: "guardian"
          });
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X POST "https://phosra-api-sandbox-production.up.railway.app/api/v1/families/f0000000-0000-4000-8000-0000000000f1/members" \
            -H "Authorization: Bearer $PHOSRA_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
            "user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
            "role": "guardian"
          }'
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.post(
              f"{BASE}/families/f0000000-0000-4000-8000-0000000000f1/members",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
              json={
                  "user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                  "role": "guardian"
              },
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"bytes"
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	body := bytes.NewBufferString(`{
            "user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
            "role": "guardian"
          }`)
          	req, _ := http.NewRequest("POST", base+"/families/f0000000-0000-4000-8000-0000000000f1/members", body)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	req.Header.Set("Content-Type", "application/json")
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: Add a user to a family with a specific role.
      tags: [Families]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [user_id, role]
              properties:
                user_id:
                  type: string
                  format: uuid
                  description: User UUID to add
                role:
                  type: string
                  enum: [owner, parent, guardian]
      responses:
        "200":
          description: Member added
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
              example:
                status: active
        "400":
          description: Invalid request body
        "403":
          description: Insufficient role to add members
        "404":
          description: Family or user not found

        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /families/{familyID}/members/{memberID}:
    parameters:
    - name: familyID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    - name: memberID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    delete:
      operationId: removeAMemberFromAFamily
      summary: Remove a member from a family
      description: "Removes a member from the family. The member loses access to the family's children and policies immediately; requires the `owner` or `admin` role."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          await phosra.members.remove("f0000000-0000-4000-8000-0000000000f1", "3fa85f64-5717-4562-b3fc-2c963f66afa6");
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X DELETE "https://phosra-api-sandbox-production.up.railway.app/api/v1/families/f0000000-0000-4000-8000-0000000000f1/members/3fa85f64-5717-4562-b3fc-2c963f66afa6" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.delete(
              f"{BASE}/families/f0000000-0000-4000-8000-0000000000f1/members/3fa85f64-5717-4562-b3fc-2c963f66afa6",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("DELETE", base+"/families/f0000000-0000-4000-8000-0000000000f1/members/3fa85f64-5717-4562-b3fc-2c963f66afa6", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Families]
      responses:
        "204":
          description: Member removed
        "403":
          description: Insufficient role to remove members
        "404":
          description: Family or member not found

  # ── Children ──────────────────────────────────────────────────────

        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /families/{familyID}/children:
    parameters:
    - name: familyID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      operationId: listChildrenInFamily
      summary: List children in family
      description: "Returns all children in the given family. Each child carries the birth date used to derive age-appropriate policies."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.children.list("f0000000-0000-4000-8000-0000000000f1");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/families/f0000000-0000-4000-8000-0000000000f1/children" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/families/f0000000-0000-4000-8000-0000000000f1/children",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/families/f0000000-0000-4000-8000-0000000000f1/children", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Children]
      responses:
        "200":
          description: Children list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Child"
              example:
              - id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                family_id: fcdf4277-ba14-4732-8db5-0369b2c88d8b
                name: Ada
                birth_date: '2015-04-10T00:00:00Z'
                created_at: '2026-06-18T14:32:07Z'
                updated_at: '2026-06-30T09:15:44Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
    post:
      operationId: addChildToFamily
      summary: Add child to family
      description: "Adds a child to the family and returns the created resource. The child's birth date is used to generate age-appropriate policy rules."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.children.create("f0000000-0000-4000-8000-0000000000f1", {
            name: "Mia",
            birth_date: "2015-06-01"
          });
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X POST "https://phosra-api-sandbox-production.up.railway.app/api/v1/families/f0000000-0000-4000-8000-0000000000f1/children" \
            -H "Authorization: Bearer $PHOSRA_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
            "name": "Mia",
            "birth_date": "2015-06-01"
          }'
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.post(
              f"{BASE}/families/f0000000-0000-4000-8000-0000000000f1/children",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
              json={
                  "name": "Mia",
                  "birth_date": "2015-06-01"
              },
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"bytes"
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	body := bytes.NewBufferString(`{
            "name": "Mia",
            "birth_date": "2015-06-01"
          }`)
          	req, _ := http.NewRequest("POST", base+"/families/f0000000-0000-4000-8000-0000000000f1/children", body)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	req.Header.Set("Content-Type", "application/json")
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Children]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, birth_date]
              properties:
                name:
                  type: string
                birth_date:
                  type: string
                  format: date
      responses:
        "201":
          description: Child added
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Child"

              example:
                id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                family_id: fcdf4277-ba14-4732-8db5-0369b2c88d8b
                name: Ada
                birth_date: '2015-04-10T00:00:00Z'
                avatar_url: https://cdn.phosra.com/avatars/ada.png
                created_at: '2026-06-18T14:32:07Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /children/{childID}:
    parameters:
    - name: childID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      operationId: getChild
      summary: Get child
      description: "Returns a single child by ID, including birth date and avatar. The caller must be a member of the child's family."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.children.get("a11ce0fa-0000-4000-8000-0000000000a1");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/children/a11ce0fa-0000-4000-8000-0000000000a1" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/children/a11ce0fa-0000-4000-8000-0000000000a1",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/children/a11ce0fa-0000-4000-8000-0000000000a1", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Children]
      responses:
        "200":
          description: Child details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Child"
              example:
                id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                family_id: fcdf4277-ba14-4732-8db5-0369b2c88d8b
                name: Ada
                birth_date: '2015-04-10T00:00:00Z'
                created_at: '2026-06-18T14:32:07Z'
                updated_at: '2026-06-30T09:15:44Z'
        "404":
          description: Child not found
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
    put:
      operationId: updateChild
      summary: Update child
      description: "Updates mutable fields (name, avatar, birth date) on a child and returns the updated resource. Changing the birth date can shift which age-appropriate rules apply."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.children.update("a11ce0fa-0000-4000-8000-0000000000a1", {
            name: "Mia Chen",
            birth_date: "2015-06-01"
          });
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X PUT "https://phosra-api-sandbox-production.up.railway.app/api/v1/children/a11ce0fa-0000-4000-8000-0000000000a1" \
            -H "Authorization: Bearer $PHOSRA_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
            "name": "Mia Chen",
            "birth_date": "2015-06-01"
          }'
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.put(
              f"{BASE}/children/a11ce0fa-0000-4000-8000-0000000000a1",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
              json={
                  "name": "Mia Chen",
                  "birth_date": "2015-06-01"
              },
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"bytes"
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	body := bytes.NewBufferString(`{
            "name": "Mia Chen",
            "birth_date": "2015-06-01"
          }`)
          	req, _ := http.NewRequest("PUT", base+"/children/a11ce0fa-0000-4000-8000-0000000000a1", body)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	req.Header.Set("Content-Type", "application/json")
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Children]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                birth_date:
                  type: string
                  format: date
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Child"
              example:
                id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                family_id: fcdf4277-ba14-4732-8db5-0369b2c88d8b
                name: Ada
                birth_date: '2015-04-10T00:00:00Z'
                avatar_url: https://cdn.phosra.com/avatars/ada.png
                created_at: '2026-06-18T14:32:07Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
    delete:
      operationId: deleteChild
      summary: Delete child
      description: "Permanently deletes a child along with all of its policies, rules, and enforcement history. This cannot be undone."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          await phosra.children.delete("a11ce0fa-0000-4000-8000-0000000000a1");
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X DELETE "https://phosra-api-sandbox-production.up.railway.app/api/v1/children/a11ce0fa-0000-4000-8000-0000000000a1" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.delete(
              f"{BASE}/children/a11ce0fa-0000-4000-8000-0000000000a1",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("DELETE", base+"/children/a11ce0fa-0000-4000-8000-0000000000a1", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Children]
      responses:
        "204":
          description: Deleted

        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /children/{childID}/age-ratings:
    parameters:
    - name: childID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      operationId: getAgeAppropriateRatingsForChild
      summary: Get age-appropriate ratings for child
      description: "Returns the content ratings appropriate for the child's current age across the supported rating systems, derived from the child's birth date."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.children.ageRatings("a11ce0fa-0000-4000-8000-0000000000a1");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/children/a11ce0fa-0000-4000-8000-0000000000a1/age-ratings" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/children/a11ce0fa-0000-4000-8000-0000000000a1/age-ratings",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/children/a11ce0fa-0000-4000-8000-0000000000a1/age-ratings", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Children]
      responses:
        "200":
          description: Age ratings
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgeRatings"

  # ── Policies ──────────────────────────────────────────────────────

              example:
                age: 11
                ratings:
                  csm:
                    id: 00000000-0000-0000-0005-000000000004
                    system_id: csm
                    code: 10+
                    name: Age 10+
                    description: Appropriate for ages 10 and up
                    min_age: 10
                    display_order: 4
                    restrictive_idx: 4
                  esrb:
                    id: 00000000-0000-0000-0003-000000000002
                    system_id: esrb
                    code: E10+
                    name: Everyone 10+
                    description: Content generally suitable for ages 10 and up
                    min_age: 10
                    display_order: 2
                    restrictive_idx: 2
                  mpaa:
                    id: 00000000-0000-0000-0001-000000000002
                    system_id: mpaa
                    code: PG
                    name: Parental Guidance
                    description: Some material may not be suitable for children
                    min_age: 0
                    display_order: 2
                    restrictive_idx: 2
                  pegi:
                    id: 00000000-0000-0000-0004-000000000002
                    system_id: pegi
                    code: '7'
                    name: PEGI 7
                    description: Suitable for ages 7 and older
                    min_age: 7
                    display_order: 2
                    restrictive_idx: 2
                  tvpg:
                    id: 00000000-0000-0000-0002-000000000004
                    system_id: tvpg
                    code: TV-PG
                    name: Parental Guidance Suggested
                    description: May contain some material parents find unsuitable
                    min_age: 0
                    display_order: 4
                    restrictive_idx: 2
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /children/{childID}/policies:
    parameters:
    - name: childID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      operationId: listChildSPolicies
      summary: List child's policies
      description: "Returns every policy attached to the child, ordered by priority. When multiple policies apply, the higher-priority policy wins."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.policies.list("a11ce0fa-0000-4000-8000-0000000000a1");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/children/a11ce0fa-0000-4000-8000-0000000000a1/policies" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/children/a11ce0fa-0000-4000-8000-0000000000a1/policies",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/children/a11ce0fa-0000-4000-8000-0000000000a1/policies", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Policies]
      responses:
        "200":
          description: Policy list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/ChildPolicy"
              example:
              - id: 7b3e9c04-2d1a-4f68-9e5b-8c07a1f34d29
                child_id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                name: "Ada's Protection Policy"
                status: active
                priority: 0
                version: 42
                created_at: '2026-06-18T14:32:07Z'
                updated_at: '2026-06-30T09:15:44Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
    post:
      operationId: createPolicyForChild
      summary: Create policy for child
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.policies.create("a11ce0fa-0000-4000-8000-0000000000a1", {
            name: "After-school limits"
          });
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X POST "https://phosra-api-sandbox-production.up.railway.app/api/v1/children/a11ce0fa-0000-4000-8000-0000000000a1/policies" \
            -H "Authorization: Bearer $PHOSRA_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
            "name": "After-school limits"
          }'
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.post(
              f"{BASE}/children/a11ce0fa-0000-4000-8000-0000000000a1/policies",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
              json={
                  "name": "After-school limits"
              },
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"bytes"
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	body := bytes.NewBufferString(`{
            "name": "After-school limits"
          }`)
          	req, _ := http.NewRequest("POST", base+"/children/a11ce0fa-0000-4000-8000-0000000000a1/policies", body)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	req.Header.Set("Content-Type", "application/json")
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: "Create a new policy for a child. Policies start in 'draft' status."
      tags: [Policies]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
      responses:
        "201":
          description: Policy created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChildPolicy"

              example:
                id: 7b3e9c04-2d1a-4f68-9e5b-8c07a1f34d29
                child_id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                name: "Ada's Protection Policy"
                status: active
                priority: 0
                version: 1
                created_at: '2026-06-18T14:32:07Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /policies/{policyID}:
    parameters:
    - name: policyID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      operationId: getPolicyDetails
      summary: Get policy details
      description: "Returns a single policy by ID, including its lifecycle status, priority weight, and version."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.policies.get("b011c1e5-0000-4000-8000-000000000b01");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/policies/b011c1e5-0000-4000-8000-000000000b01" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/policies/b011c1e5-0000-4000-8000-000000000b01",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/policies/b011c1e5-0000-4000-8000-000000000b01", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Policies]
      responses:
        "200":
          description: Policy details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChildPolicy"
              example:
                id: 7b3e9c04-2d1a-4f68-9e5b-8c07a1f34d29
                child_id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                name: "Ada's Protection Policy"
                status: active
                priority: 0
                version: 42
                created_at: '2026-06-18T14:32:07Z'
                updated_at: '2026-06-30T09:15:44Z'
        "404":
          description: Policy not found
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
    put:
      operationId: updateAPolicySNameOrPriority
      summary: Update a policy's name or priority
      description: "Updates a policy's display name or priority weight and returns the updated resource. Priority decides which policy wins when several apply to the same child."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.policies.update("b011c1e5-0000-4000-8000-000000000b01", {
            name: "After-school limits",
            priority: 100
          });
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X PUT "https://phosra-api-sandbox-production.up.railway.app/api/v1/policies/b011c1e5-0000-4000-8000-000000000b01" \
            -H "Authorization: Bearer $PHOSRA_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
            "name": "After-school limits",
            "priority": 100
          }'
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.put(
              f"{BASE}/policies/b011c1e5-0000-4000-8000-000000000b01",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
              json={
                  "name": "After-school limits",
                  "priority": 100
              },
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"bytes"
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	body := bytes.NewBufferString(`{
            "name": "After-school limits",
            "priority": 100
          }`)
          	req, _ := http.NewRequest("PUT", base+"/policies/b011c1e5-0000-4000-8000-000000000b01", body)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	req.Header.Set("Content-Type", "application/json")
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Policies]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                priority:
                  type: integer
      responses:
        "200":
          description: Policy updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChildPolicy"
              example:
                id: 7b3e9c04-2d1a-4f68-9e5b-8c07a1f34d29
                child_id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                name: "Ada's Protection Policy"
                status: active
                priority: 0
                version: 1
                created_at: '2026-06-18T14:32:07Z'
        "404":
          description: Policy not found
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
    delete:
      operationId: deleteAPolicyAndAllItsRules
      summary: Delete a policy and all its rules
      description: "Permanently deletes a policy together with all of its rules. Enforcement derived from the policy stops; this cannot be undone."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          await phosra.policies.delete("b011c1e5-0000-4000-8000-000000000b01");
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X DELETE "https://phosra-api-sandbox-production.up.railway.app/api/v1/policies/b011c1e5-0000-4000-8000-000000000b01" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.delete(
              f"{BASE}/policies/b011c1e5-0000-4000-8000-000000000b01",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("DELETE", base+"/policies/b011c1e5-0000-4000-8000-000000000b01", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Policies]
      responses:
        "204":
          description: Deleted
        "404":
          description: Policy not found

        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /policies/{policyID}/activate:
    post:
      operationId: activatePolicy
      summary: Activate policy
      description: "Transitions the policy to the `active` state so its rules are enforced, and returns the updated policy."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.policies.activate("b011c1e5-0000-4000-8000-000000000b01");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X POST "https://phosra-api-sandbox-production.up.railway.app/api/v1/policies/b011c1e5-0000-4000-8000-000000000b01/activate" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.post(
              f"{BASE}/policies/b011c1e5-0000-4000-8000-000000000b01/activate",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("POST", base+"/policies/b011c1e5-0000-4000-8000-000000000b01/activate", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Policies]
      parameters:
      - name: policyID
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        "200":
          description: Policy activated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChildPolicy"

              example:
                id: 7b3e9c04-2d1a-4f68-9e5b-8c07a1f34d29
                child_id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                name: "Ada's Protection Policy"
                status: active
                priority: 0
                version: 1
                created_at: '2026-06-18T14:32:07Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /policies/{policyID}/pause:
    post:
      operationId: pausePolicy
      summary: Pause policy
      description: "Transitions the policy to the `paused` state, suspending enforcement of its rules without deleting them, and returns the updated policy."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.policies.pause("b011c1e5-0000-4000-8000-000000000b01");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X POST "https://phosra-api-sandbox-production.up.railway.app/api/v1/policies/b011c1e5-0000-4000-8000-000000000b01/pause" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.post(
              f"{BASE}/policies/b011c1e5-0000-4000-8000-000000000b01/pause",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("POST", base+"/policies/b011c1e5-0000-4000-8000-000000000b01/pause", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Policies]
      parameters:
      - name: policyID
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        "200":
          description: Policy paused
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChildPolicy"

              example:
                id: 7b3e9c04-2d1a-4f68-9e5b-8c07a1f34d29
                child_id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                name: "Ada's Protection Policy"
                status: active
                priority: 0
                version: 1
                created_at: '2026-06-18T14:32:07Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /policies/{policyID}/generate-from-age:
    post:
      operationId: autoGenerateRulesBasedOnChildSAge
      summary: Auto-generate rules based on child's age
      description: "Generates a starter set of rules appropriate for the child's age and appends them to the policy. Existing rules are preserved."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.policies.generateFromAge("b011c1e5-0000-4000-8000-000000000b01");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X POST "https://phosra-api-sandbox-production.up.railway.app/api/v1/policies/b011c1e5-0000-4000-8000-000000000b01/generate-from-age" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.post(
              f"{BASE}/policies/b011c1e5-0000-4000-8000-000000000b01/generate-from-age",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("POST", base+"/policies/b011c1e5-0000-4000-8000-000000000b01/generate-from-age", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Policies]
      parameters:
      - name: policyID
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        "200":
          description: Rules generated
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/PolicyRule"

  # ── Policy Rules ──────────────────────────────────────────────────

              example:
              - id: 3d92f6b1-8e04-4a7c-b1d5-6f2093c8ae41
                policy_id: 7b3e9c04-2d1a-4f68-9e5b-8c07a1f34d29
                category: rating_age_gate
                enabled: true
                config: {}
                created_at: '2026-06-18T14:32:07Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /policies/{policyID}/rules:
    parameters:
    - name: policyID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      operationId: listPolicyRules
      summary: List policy rules
      description: "Returns all rules in the policy. Each rule maps to one of the OCSS rule categories and carries its category-specific configuration."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.rules.list("b011c1e5-0000-4000-8000-000000000b01");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/policies/b011c1e5-0000-4000-8000-000000000b01/rules" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/policies/b011c1e5-0000-4000-8000-000000000b01/rules",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/policies/b011c1e5-0000-4000-8000-000000000b01/rules", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Policy Rules]
      responses:
        "200":
          description: Rule list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/PolicyRule"
              example:
              - id: d1aa00db-2dca-4e73-a8d5-63200ff56fc1
                policy_id: 7b3e9c04-2d1a-4f68-9e5b-8c07a1f34d29
                category: addictive_design_control
                enabled: true
                config:
                  disable_streaks: true
                  disable_autoplay: true
                  disable_like_counts: true
                  disable_daily_rewards: true
                  disable_infinite_scroll: true
                created_at: '2026-06-18T14:32:07Z'
                updated_at: '2026-06-30T09:15:44Z'
              - id: 851d8344-cc17-458e-9c1b-1a4633267e1b
                policy_id: 7b3e9c04-2d1a-4f68-9e5b-8c07a1f34d29
                category: age_gate
                enabled: true
                config:
                  enabled: true
                  min_age: 13
                created_at: '2026-06-18T14:32:07Z'
                updated_at: '2026-06-30T09:15:44Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
    post:
      operationId: createRule
      summary: Create rule
      description: "Adds a rule to the policy and returns the created resource. The `category` must be one of the OCSS rule categories."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.rules.create("b011c1e5-0000-4000-8000-000000000b01", {
            category: "time_daily_limit",
            enabled: true,
            config: {
              minutes: 120
            }
          });
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X POST "https://phosra-api-sandbox-production.up.railway.app/api/v1/policies/b011c1e5-0000-4000-8000-000000000b01/rules" \
            -H "Authorization: Bearer $PHOSRA_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
            "category": "time_daily_limit",
            "enabled": true,
            "config": {
              "minutes": 120
            }
          }'
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.post(
              f"{BASE}/policies/b011c1e5-0000-4000-8000-000000000b01/rules",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
              json={
                  "category": "time_daily_limit",
                  "enabled": True,
                  "config": {
                      "minutes": 120
                  }
              },
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"bytes"
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	body := bytes.NewBufferString(`{
            "category": "time_daily_limit",
            "enabled": true,
            "config": {
              "minutes": 120
            }
          }`)
          	req, _ := http.NewRequest("POST", base+"/policies/b011c1e5-0000-4000-8000-000000000b01/rules", body)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	req.Header.Set("Content-Type", "application/json")
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Policy Rules]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [category, enabled, config]
              properties:
                category:
                  $ref: "#/components/schemas/RuleCategory"
                enabled:
                  type: boolean
                config:
                  type: object
                  description: Category-specific configuration JSON
      responses:
        "201":
          description: Rule created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PolicyRule"

              example:
                id: 3d92f6b1-8e04-4a7c-b1d5-6f2093c8ae41
                policy_id: 7b3e9c04-2d1a-4f68-9e5b-8c07a1f34d29
                category: rating_age_gate
                enabled: true
                config: {}
                created_at: '2026-06-18T14:32:07Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /policies/{policyID}/rules/bulk:
    put:
      operationId: bulkUpsertRules
      summary: Bulk upsert rules
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.rules.bulkUpsert("b011c1e5-0000-4000-8000-000000000b01", {
            rules: [
              {
                category: "time_daily_limit",
                enabled: true,
                config: {
                  minutes: 120
                }
              },
              {
                category: "web_safesearch",
                enabled: true,
                config: {}
              }
            ]
          });
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X PUT "https://phosra-api-sandbox-production.up.railway.app/api/v1/policies/b011c1e5-0000-4000-8000-000000000b01/rules/bulk" \
            -H "Authorization: Bearer $PHOSRA_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
            "rules": [
              {
                "category": "time_daily_limit",
                "enabled": true,
                "config": {
                  "minutes": 120
                }
              },
              {
                "category": "web_safesearch",
                "enabled": true,
                "config": {}
              }
            ]
          }'
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.put(
              f"{BASE}/policies/b011c1e5-0000-4000-8000-000000000b01/rules/bulk",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
              json={
                  "rules": [
                      {
                          "category": "time_daily_limit",
                          "enabled": True,
                          "config": {
                              "minutes": 120
                          }
                      },
                      {
                          "category": "web_safesearch",
                          "enabled": True,
                          "config": {}
                      }
                  ]
              },
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"bytes"
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	body := bytes.NewBufferString(`{
            "rules": [
              {
                "category": "time_daily_limit",
                "enabled": true,
                "config": {
                  "minutes": 120
                }
              },
              {
                "category": "web_safesearch",
                "enabled": true,
                "config": {}
              }
            ]
          }`)
          	req, _ := http.NewRequest("PUT", base+"/policies/b011c1e5-0000-4000-8000-000000000b01/rules/bulk", body)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	req.Header.Set("Content-Type", "application/json")
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: Create or update multiple rules at once for a policy. Each rule in the array must include category, enabled, and config.
      tags: [Policy Rules]
      parameters:
      - name: policyID
        in: path
        required: true
        schema:
          type: string
          format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [rules]
              properties:
                rules:
                  type: array
                  items:
                    type: object
                    required: [category, enabled, config]
                    properties:
                      category:
                        $ref: "#/components/schemas/RuleCategory"
                      enabled:
                        type: boolean
                      config:
                        type: object
      responses:
        "200":
          description: Rules upserted
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/PolicyRule"

              example:
              - id: 3d92f6b1-8e04-4a7c-b1d5-6f2093c8ae41
                policy_id: 7b3e9c04-2d1a-4f68-9e5b-8c07a1f34d29
                category: rating_age_gate
                enabled: true
                config: {}
                created_at: '2026-06-18T14:32:07Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /rules/{ruleID}:
    parameters:
    - name: ruleID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    put:
      operationId: updateARuleSEnabledStatusOrConfig
      summary: Update a rule's enabled status or config
      description: "Updates a rule's enabled flag or category-specific configuration and returns the updated resource."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.rules.update("7c9e6679-7425-40de-944b-e07fc1f90ae7", {
            enabled: false,
            config: {
              minutes: 90
            }
          });
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X PUT "https://phosra-api-sandbox-production.up.railway.app/api/v1/rules/7c9e6679-7425-40de-944b-e07fc1f90ae7" \
            -H "Authorization: Bearer $PHOSRA_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
            "enabled": false,
            "config": {
              "minutes": 90
            }
          }'
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.put(
              f"{BASE}/rules/7c9e6679-7425-40de-944b-e07fc1f90ae7",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
              json={
                  "enabled": False,
                  "config": {
                      "minutes": 90
                  }
              },
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"bytes"
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	body := bytes.NewBufferString(`{
            "enabled": false,
            "config": {
              "minutes": 90
            }
          }`)
          	req, _ := http.NewRequest("PUT", base+"/rules/7c9e6679-7425-40de-944b-e07fc1f90ae7", body)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	req.Header.Set("Content-Type", "application/json")
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Policy Rules]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                enabled:
                  type: boolean
                config:
                  type: object
                  description: Updated configuration JSON
      responses:
        "200":
          description: Rule updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PolicyRule"
              example:
                id: 3d92f6b1-8e04-4a7c-b1d5-6f2093c8ae41
                policy_id: 7b3e9c04-2d1a-4f68-9e5b-8c07a1f34d29
                category: rating_age_gate
                enabled: true
                config: {}
                created_at: '2026-06-18T14:32:07Z'
        "404":
          description: Rule not found
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
    delete:
      operationId: deleteARule
      summary: Delete a rule
      description: "Permanently removes a rule from its policy. Enforcement stops applying that rule on the next sync."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          await phosra.rules.delete("7c9e6679-7425-40de-944b-e07fc1f90ae7");
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X DELETE "https://phosra-api-sandbox-production.up.railway.app/api/v1/rules/7c9e6679-7425-40de-944b-e07fc1f90ae7" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.delete(
              f"{BASE}/rules/7c9e6679-7425-40de-944b-e07fc1f90ae7",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("DELETE", base+"/rules/7c9e6679-7425-40de-944b-e07fc1f90ae7", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Policy Rules]
      responses:
        "204":
          description: Rule deleted
        "404":
          description: Rule not found

  # ── Platforms ─────────────────────────────────────────────────────

        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /platforms:
    get:
      operationId: listAllPlatforms
      summary: List all platforms
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.platforms.list();
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/platforms"
      - lang: Python
        label: Python
        source: |
          import requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/platforms",
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/platforms", nil)
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: List all platforms Phosra can integrate with, including category, compliance tier, auth type, and capabilities.
      tags: [Platforms]
      security: []
      responses:
        "200":
          description: Platform list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Platform"

              example:
              - id: fire_tablet
                name: Amazon Fire Tablet
                category: device
                tier: pending
                description: Amazon Fire Tablet Kids parental controls
                icon_url: ''
                auth_type: manual
                enabled: true
                capabilities:
                - content_rating
                - time_limit
                - scheduled_hours
                - web_filtering
                - safe_search
                - app_control
                - purchase_control
                enforcement_mode: manual_attested
                rule_support:
                  addictive_design_control: unsupported
                  addictive_pattern_block: unsupported
                  adult_site_av_required: unsupported
                  age_appropriate_profile_mode: unsupported
                  age_gate: unsupported
                  age_signal_broadcast: unsupported
                  ai_chatbot_age_assertion: unsupported
                  ai_chatbot_tier_gate: unsupported
                  ai_companion_block: unsupported
                  ai_data_responsibility_rating_threshold: unsupported
                  ai_minor_interaction: unsupported
                  ai_no_csam_generation: unsupported
                  ai_no_engagement_dark_patterns: unsupported
                  ai_no_personhood_deception: unsupported
                  ai_no_self_harm_promotion: unsupported
                  ai_no_simulated_companionship: unsupported
                  ai_no_unsupervised_therapy: unsupported
                  ai_product_classification_gate: unsupported
                  ai_safety_rating_threshold: unsupported
                  ai_synthetic_media_disclosure: unsupported
                  ai_toy_safety_cert: unsupported
                  ai_training_data_opt_out: unsupported
                  ai_transparency_rating_threshold: unsupported
                  algo_feed_control: unsupported
                  algorithmic_audit: unsupported
                  app_install_block: unsupported
                  app_store_age_attestation: unsupported
                  autoplay_disable: unsupported
                  cipa_filter_attestation: unsupported
                  commercial_data_ban: unsupported
                  commercial_pressure_threshold: unsupported
                  content_allow_title: guided
                  content_allowlist_mode: guided
                  content_block_title: guided
                  content_descriptor_block: guided
                  content_rating: guided
                  crisis_resource_surface: unsupported
                  csam_reporting: unsupported
                  data_deletion_request: unsupported
                  data_minimization_enforce: unsupported
                  digital_literacy_curriculum_link: unsupported
                  dm_feature_disable: unsupported
                  dm_restriction: unsupported
                  dpia_request_generator: unsupported
                  dumbphone_migration_mode: unsupported
                  educational_credit: unsupported
                  ephemeral_message_disable: unsupported
                  esafety_commission_reporting: unsupported
                  firearm_content_block: unsupported
                  flagged_content_digest: unsupported
                  gambling_mechanics_block: unsupported
                  geolocation_opt_in: unsupported
                  geolocation_precision_cap: unsupported
                  hard_id_verification_escalation: unsupported
                  harm_report_intake: unsupported
                  image_content_monitoring: unsupported
                  image_rights_minor: unsupported
                  infinite_scroll_block: unsupported
                  library_filter_compliance: unsupported
                  message_content_monitoring: unsupported
                  minor_data_sale_audit_log: unsupported
                  monitoring_activity: unsupported
                  monitoring_alerts: unsupported
                  named_contact_block: unsupported
                  ncii_takedown: unsupported
                  not_for_minors_block: unsupported
                  notification_curfew: unsupported
                  os_age_signal_ingest: unsupported
                  parental_attestation_cert: unsupported
                  parental_consent_gate: unsupported
                  parental_event_notification: unsupported
                  phone_free_school_hours: unsupported
                  platform_liability_evidence_capture: unsupported
                  presence_status_hide: unsupported
                  privacy_account_creation: unsupported
                  privacy_data_sharing: unsupported
                  privacy_location: unsupported
                  privacy_profile_visibility: unsupported
                  privacy_rating_dimension_gate: unsupported
                  privacy_rating_score_threshold: unsupported
                  privacy_rating_tier_gate: unsupported
                  privacy_seal_required: unsupported
                  profanity_threshold: unsupported
                  prosocial_weight: unsupported
                  purchase_approval: guided
                  purchase_block_iap: guided
                  purchase_spending_cap: guided
                  rating_age_gate: unsupported
                  recognized_seal_allowlist: unsupported
                  representation_weight: unsupported
                  role_model_weight: unsupported
                  school_alert_routing: unsupported
                  screen_time_report: unsupported
                  search_query_monitoring: unsupported
                  sexual_content_threshold: unsupported
                  social_account_content_monitoring: unsupported
                  social_chat_control: unsupported
                  social_contacts: unsupported
                  social_media_min_age: unsupported
                  social_media_warning_label_render: unsupported
                  social_multiplayer: unsupported
                  stranger_dm_block: unsupported
                  stranger_outreach_friction: unsupported
                  streaming_age_rating_enforce: unsupported
                  student_privacy_school_mode: unsupported
                  substance_content_threshold: unsupported
                  targeted_ad_block: unsupported
                  teen_minimum_age_16_gate: unsupported
                  third_party_tracker_block: unsupported
                  time_daily_limit: guided
                  time_downtime: guided
                  time_per_app_limit: guided
                  time_scheduled_hours: guided
                  ugc_world_age_gate: unsupported
                  unrated_privacy_default: unsupported
                  usage_timer_notification: unsupported
                  violence_threshold: unsupported
                  voice_chat_minor_limit: unsupported
                  web_category_block: guided
                  web_custom_allowlist: guided
                  web_custom_blocklist: guided
                  web_filter_level: guided
                  web_safesearch: guided
              - id: fire_tv
                name: Amazon Fire TV Stick
                category: device
                tier: pending
                description: Amazon Fire TV parental controls
                icon_url: ''
                auth_type: manual
                enabled: true
                capabilities:
                - content_rating
                - app_control
                - purchase_control
                enforcement_mode: manual_attested
                rule_support:
                  addictive_design_control: unsupported
                  addictive_pattern_block: unsupported
                  adult_site_av_required: unsupported
                  age_appropriate_profile_mode: unsupported
                  age_gate: unsupported
                  age_signal_broadcast: unsupported
                  ai_chatbot_age_assertion: unsupported
                  ai_chatbot_tier_gate: unsupported
                  ai_companion_block: unsupported
                  ai_data_responsibility_rating_threshold: unsupported
                  ai_minor_interaction: unsupported
                  ai_no_csam_generation: unsupported
                  ai_no_engagement_dark_patterns: unsupported
                  ai_no_personhood_deception: unsupported
                  ai_no_self_harm_promotion: unsupported
                  ai_no_simulated_companionship: unsupported
                  ai_no_unsupervised_therapy: unsupported
                  ai_product_classification_gate: unsupported
                  ai_safety_rating_threshold: unsupported
                  ai_synthetic_media_disclosure: unsupported
                  ai_toy_safety_cert: unsupported
                  ai_training_data_opt_out: unsupported
                  ai_transparency_rating_threshold: unsupported
                  algo_feed_control: unsupported
                  algorithmic_audit: unsupported
                  app_install_block: unsupported
                  app_store_age_attestation: unsupported
                  autoplay_disable: unsupported
                  cipa_filter_attestation: unsupported
                  commercial_data_ban: unsupported
                  commercial_pressure_threshold: unsupported
                  content_allow_title: guided
                  content_allowlist_mode: guided
                  content_block_title: guided
                  content_descriptor_block: guided
                  content_rating: guided
                  crisis_resource_surface: unsupported
                  csam_reporting: unsupported
                  data_deletion_request: unsupported
                  data_minimization_enforce: unsupported
                  digital_literacy_curriculum_link: unsupported
                  dm_feature_disable: unsupported
                  dm_restriction: unsupported
                  dpia_request_generator: unsupported
                  dumbphone_migration_mode: unsupported
                  educational_credit: unsupported
                  ephemeral_message_disable: unsupported
                  esafety_commission_reporting: unsupported
                  firearm_content_block: unsupported
                  flagged_content_digest: unsupported
                  gambling_mechanics_block: unsupported
                  geolocation_opt_in: unsupported
                  geolocation_precision_cap: unsupported
                  hard_id_verification_escalation: unsupported
                  harm_report_intake: unsupported
                  image_content_monitoring: unsupported
                  image_rights_minor: unsupported
                  infinite_scroll_block: unsupported
                  library_filter_compliance: unsupported
                  message_content_monitoring: unsupported
                  minor_data_sale_audit_log: unsupported
                  monitoring_activity: unsupported
                  monitoring_alerts: unsupported
                  named_contact_block: unsupported
                  ncii_takedown: unsupported
                  not_for_minors_block: unsupported
                  notification_curfew: unsupported
                  os_age_signal_ingest: unsupported
                  parental_attestation_cert: unsupported
                  parental_consent_gate: unsupported
                  parental_event_notification: unsupported
                  phone_free_school_hours: unsupported
                  platform_liability_evidence_capture: unsupported
                  presence_status_hide: unsupported
                  privacy_account_creation: unsupported
                  privacy_data_sharing: unsupported
                  privacy_location: unsupported
                  privacy_profile_visibility: unsupported
                  privacy_rating_dimension_gate: unsupported
                  privacy_rating_score_threshold: unsupported
                  privacy_rating_tier_gate: unsupported
                  privacy_seal_required: unsupported
                  profanity_threshold: unsupported
                  prosocial_weight: unsupported
                  purchase_approval: guided
                  purchase_block_iap: guided
                  purchase_spending_cap: guided
                  rating_age_gate: unsupported
                  recognized_seal_allowlist: unsupported
                  representation_weight: unsupported
                  role_model_weight: unsupported
                  school_alert_routing: unsupported
                  screen_time_report: unsupported
                  search_query_monitoring: unsupported
                  sexual_content_threshold: unsupported
                  social_account_content_monitoring: unsupported
                  social_chat_control: unsupported
                  social_contacts: unsupported
                  social_media_min_age: unsupported
                  social_media_warning_label_render: unsupported
                  social_multiplayer: unsupported
                  stranger_dm_block: unsupported
                  stranger_outreach_friction: unsupported
                  streaming_age_rating_enforce: unsupported
                  student_privacy_school_mode: unsupported
                  substance_content_threshold: unsupported
                  targeted_ad_block: unsupported
                  teen_minimum_age_16_gate: unsupported
                  third_party_tracker_block: unsupported
                  time_daily_limit: unsupported
                  time_downtime: unsupported
                  time_per_app_limit: unsupported
                  time_scheduled_hours: unsupported
                  ugc_world_age_gate: unsupported
                  unrated_privacy_default: unsupported
                  usage_timer_notification: unsupported
                  violence_threshold: unsupported
                  voice_chat_minor_limit: unsupported
                  web_category_block: unsupported
                  web_custom_allowlist: unsupported
                  web_custom_blocklist: unsupported
                  web_filter_level: unsupported
                  web_safesearch: unsupported
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '502':
          $ref: '#/components/responses/BadGateway'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '500':
          $ref: '#/components/responses/ServerError'
  /platforms/{platformID}:
    parameters:
    - name: platformID
      in: path
      required: true
      schema:
        type: string
    get:
      operationId: getPlatformDetails
      summary: Get platform details
      description: "Returns the catalog entry for a single platform, including its category, enforcement capabilities, and OAuth support."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.platforms.get("fire_tablet");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/platforms/fire_tablet"
      - lang: Python
        label: Python
        source: |
          import requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/platforms/fire_tablet",
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/platforms/fire_tablet", nil)
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Platforms]
      security: []
      responses:
        "200":
          description: Platform details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Platform"
              example:
                id: nextdns
                name: NextDNS
                category: dns
                tier: compliant
                description: DNS-level content filtering and SafeSearch enforcement for family networks.
                icon_url: https://cdn.phosra.com/platforms/nextdns.svg
                auth_type: api_key
                enabled: true
                enforcement_mode: dns
                rule_support: {}
        "404":
          description: Platform not found

        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '429':
          $ref: '#/components/responses/RateLimited'
        '502':
          $ref: '#/components/responses/BadGateway'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '500':
          $ref: '#/components/responses/ServerError'
  /platforms/by-category:
    get:
      operationId: filterPlatformsByCategory
      summary: Filter platforms by category
      description: "Returns platforms in the catalog that belong to the given category (for example `streaming` or `social`)."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.platforms.byCategory("device");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/platforms/by-category?category=device"
      - lang: Python
        label: Python
        source: |
          import requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/platforms/by-category?category=device",
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/platforms/by-category?category=device", nil)
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Platforms]
      security: []
      parameters:
      - name: category
        in: query
        required: true
        schema:
          type: string
          enum: [dns, streaming, gaming, device, browser]
      responses:
        "200":
          description: Filtered platform list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Platform"

              example:
              - id: fire_tablet
                name: Amazon Fire Tablet
                category: device
                tier: pending
                description: Amazon Fire Tablet Kids parental controls
                icon_url: ''
                auth_type: manual
                enabled: true
                capabilities:
                - content_rating
                - time_limit
                - scheduled_hours
                - web_filtering
                - safe_search
                - app_control
                - purchase_control
                enforcement_mode: manual_attested
                rule_support:
                  addictive_design_control: unsupported
                  addictive_pattern_block: unsupported
                  adult_site_av_required: unsupported
                  age_appropriate_profile_mode: unsupported
                  age_gate: unsupported
                  age_signal_broadcast: unsupported
                  ai_chatbot_age_assertion: unsupported
                  ai_chatbot_tier_gate: unsupported
                  ai_companion_block: unsupported
                  ai_data_responsibility_rating_threshold: unsupported
                  ai_minor_interaction: unsupported
                  ai_no_csam_generation: unsupported
                  ai_no_engagement_dark_patterns: unsupported
                  ai_no_personhood_deception: unsupported
                  ai_no_self_harm_promotion: unsupported
                  ai_no_simulated_companionship: unsupported
                  ai_no_unsupervised_therapy: unsupported
                  ai_product_classification_gate: unsupported
                  ai_safety_rating_threshold: unsupported
                  ai_synthetic_media_disclosure: unsupported
                  ai_toy_safety_cert: unsupported
                  ai_training_data_opt_out: unsupported
                  ai_transparency_rating_threshold: unsupported
                  algo_feed_control: unsupported
                  algorithmic_audit: unsupported
                  app_install_block: unsupported
                  app_store_age_attestation: unsupported
                  autoplay_disable: unsupported
                  cipa_filter_attestation: unsupported
                  commercial_data_ban: unsupported
                  commercial_pressure_threshold: unsupported
                  content_allow_title: guided
                  content_allowlist_mode: guided
                  content_block_title: guided
                  content_descriptor_block: guided
                  content_rating: guided
                  crisis_resource_surface: unsupported
                  csam_reporting: unsupported
                  data_deletion_request: unsupported
                  data_minimization_enforce: unsupported
                  digital_literacy_curriculum_link: unsupported
                  dm_feature_disable: unsupported
                  dm_restriction: unsupported
                  dpia_request_generator: unsupported
                  dumbphone_migration_mode: unsupported
                  educational_credit: unsupported
                  ephemeral_message_disable: unsupported
                  esafety_commission_reporting: unsupported
                  firearm_content_block: unsupported
                  flagged_content_digest: unsupported
                  gambling_mechanics_block: unsupported
                  geolocation_opt_in: unsupported
                  geolocation_precision_cap: unsupported
                  hard_id_verification_escalation: unsupported
                  harm_report_intake: unsupported
                  image_content_monitoring: unsupported
                  image_rights_minor: unsupported
                  infinite_scroll_block: unsupported
                  library_filter_compliance: unsupported
                  message_content_monitoring: unsupported
                  minor_data_sale_audit_log: unsupported
                  monitoring_activity: unsupported
                  monitoring_alerts: unsupported
                  named_contact_block: unsupported
                  ncii_takedown: unsupported
                  not_for_minors_block: unsupported
                  notification_curfew: unsupported
                  os_age_signal_ingest: unsupported
                  parental_attestation_cert: unsupported
                  parental_consent_gate: unsupported
                  parental_event_notification: unsupported
                  phone_free_school_hours: unsupported
                  platform_liability_evidence_capture: unsupported
                  presence_status_hide: unsupported
                  privacy_account_creation: unsupported
                  privacy_data_sharing: unsupported
                  privacy_location: unsupported
                  privacy_profile_visibility: unsupported
                  privacy_rating_dimension_gate: unsupported
                  privacy_rating_score_threshold: unsupported
                  privacy_rating_tier_gate: unsupported
                  privacy_seal_required: unsupported
                  profanity_threshold: unsupported
                  prosocial_weight: unsupported
                  purchase_approval: guided
                  purchase_block_iap: guided
                  purchase_spending_cap: guided
                  rating_age_gate: unsupported
                  recognized_seal_allowlist: unsupported
                  representation_weight: unsupported
                  role_model_weight: unsupported
                  school_alert_routing: unsupported
                  screen_time_report: unsupported
                  search_query_monitoring: unsupported
                  sexual_content_threshold: unsupported
                  social_account_content_monitoring: unsupported
                  social_chat_control: unsupported
                  social_contacts: unsupported
                  social_media_min_age: unsupported
                  social_media_warning_label_render: unsupported
                  social_multiplayer: unsupported
                  stranger_dm_block: unsupported
                  stranger_outreach_friction: unsupported
                  streaming_age_rating_enforce: unsupported
                  student_privacy_school_mode: unsupported
                  substance_content_threshold: unsupported
                  targeted_ad_block: unsupported
                  teen_minimum_age_16_gate: unsupported
                  third_party_tracker_block: unsupported
                  time_daily_limit: guided
                  time_downtime: guided
                  time_per_app_limit: guided
                  time_scheduled_hours: guided
                  ugc_world_age_gate: unsupported
                  unrated_privacy_default: unsupported
                  usage_timer_notification: unsupported
                  violence_threshold: unsupported
                  voice_chat_minor_limit: unsupported
                  web_category_block: guided
                  web_custom_allowlist: guided
                  web_custom_blocklist: guided
                  web_filter_level: guided
                  web_safesearch: guided
              - id: fire_tv
                name: Amazon Fire TV Stick
                category: device
                tier: pending
                description: Amazon Fire TV parental controls
                icon_url: ''
                auth_type: manual
                enabled: true
                capabilities:
                - content_rating
                - app_control
                - purchase_control
                enforcement_mode: manual_attested
                rule_support:
                  addictive_design_control: unsupported
                  addictive_pattern_block: unsupported
                  adult_site_av_required: unsupported
                  age_appropriate_profile_mode: unsupported
                  age_gate: unsupported
                  age_signal_broadcast: unsupported
                  ai_chatbot_age_assertion: unsupported
                  ai_chatbot_tier_gate: unsupported
                  ai_companion_block: unsupported
                  ai_data_responsibility_rating_threshold: unsupported
                  ai_minor_interaction: unsupported
                  ai_no_csam_generation: unsupported
                  ai_no_engagement_dark_patterns: unsupported
                  ai_no_personhood_deception: unsupported
                  ai_no_self_harm_promotion: unsupported
                  ai_no_simulated_companionship: unsupported
                  ai_no_unsupervised_therapy: unsupported
                  ai_product_classification_gate: unsupported
                  ai_safety_rating_threshold: unsupported
                  ai_synthetic_media_disclosure: unsupported
                  ai_toy_safety_cert: unsupported
                  ai_training_data_opt_out: unsupported
                  ai_transparency_rating_threshold: unsupported
                  algo_feed_control: unsupported
                  algorithmic_audit: unsupported
                  app_install_block: unsupported
                  app_store_age_attestation: unsupported
                  autoplay_disable: unsupported
                  cipa_filter_attestation: unsupported
                  commercial_data_ban: unsupported
                  commercial_pressure_threshold: unsupported
                  content_allow_title: guided
                  content_allowlist_mode: guided
                  content_block_title: guided
                  content_descriptor_block: guided
                  content_rating: guided
                  crisis_resource_surface: unsupported
                  csam_reporting: unsupported
                  data_deletion_request: unsupported
                  data_minimization_enforce: unsupported
                  digital_literacy_curriculum_link: unsupported
                  dm_feature_disable: unsupported
                  dm_restriction: unsupported
                  dpia_request_generator: unsupported
                  dumbphone_migration_mode: unsupported
                  educational_credit: unsupported
                  ephemeral_message_disable: unsupported
                  esafety_commission_reporting: unsupported
                  firearm_content_block: unsupported
                  flagged_content_digest: unsupported
                  gambling_mechanics_block: unsupported
                  geolocation_opt_in: unsupported
                  geolocation_precision_cap: unsupported
                  hard_id_verification_escalation: unsupported
                  harm_report_intake: unsupported
                  image_content_monitoring: unsupported
                  image_rights_minor: unsupported
                  infinite_scroll_block: unsupported
                  library_filter_compliance: unsupported
                  message_content_monitoring: unsupported
                  minor_data_sale_audit_log: unsupported
                  monitoring_activity: unsupported
                  monitoring_alerts: unsupported
                  named_contact_block: unsupported
                  ncii_takedown: unsupported
                  not_for_minors_block: unsupported
                  notification_curfew: unsupported
                  os_age_signal_ingest: unsupported
                  parental_attestation_cert: unsupported
                  parental_consent_gate: unsupported
                  parental_event_notification: unsupported
                  phone_free_school_hours: unsupported
                  platform_liability_evidence_capture: unsupported
                  presence_status_hide: unsupported
                  privacy_account_creation: unsupported
                  privacy_data_sharing: unsupported
                  privacy_location: unsupported
                  privacy_profile_visibility: unsupported
                  privacy_rating_dimension_gate: unsupported
                  privacy_rating_score_threshold: unsupported
                  privacy_rating_tier_gate: unsupported
                  privacy_seal_required: unsupported
                  profanity_threshold: unsupported
                  prosocial_weight: unsupported
                  purchase_approval: guided
                  purchase_block_iap: guided
                  purchase_spending_cap: guided
                  rating_age_gate: unsupported
                  recognized_seal_allowlist: unsupported
                  representation_weight: unsupported
                  role_model_weight: unsupported
                  school_alert_routing: unsupported
                  screen_time_report: unsupported
                  search_query_monitoring: unsupported
                  sexual_content_threshold: unsupported
                  social_account_content_monitoring: unsupported
                  social_chat_control: unsupported
                  social_contacts: unsupported
                  social_media_min_age: unsupported
                  social_media_warning_label_render: unsupported
                  social_multiplayer: unsupported
                  stranger_dm_block: unsupported
                  stranger_outreach_friction: unsupported
                  streaming_age_rating_enforce: unsupported
                  student_privacy_school_mode: unsupported
                  substance_content_threshold: unsupported
                  targeted_ad_block: unsupported
                  teen_minimum_age_16_gate: unsupported
                  third_party_tracker_block: unsupported
                  time_daily_limit: unsupported
                  time_downtime: unsupported
                  time_per_app_limit: unsupported
                  time_scheduled_hours: unsupported
                  ugc_world_age_gate: unsupported
                  unrated_privacy_default: unsupported
                  usage_timer_notification: unsupported
                  violence_threshold: unsupported
                  voice_chat_minor_limit: unsupported
                  web_category_block: unsupported
                  web_custom_allowlist: unsupported
                  web_custom_blocklist: unsupported
                  web_filter_level: unsupported
                  web_safesearch: unsupported
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '502':
          $ref: '#/components/responses/BadGateway'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '500':
          $ref: '#/components/responses/ServerError'
  /providers/{did}/connect:
    parameters:
    - name: did
      in: path
      required: true
      description: >-
        The provider's OCSS DID, e.g. `did:ocss:loopline`. Colons are path-safe
        (RFC 3986) — send them literally, do not percent-encode.
      schema:
        type: string
        pattern: "^did:ocss:[a-z0-9-]+$"
    get:
      operationId: getProviderConnectConfig
      summary: Get provider connect config
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/providers/did:ocss:loopline/connect"
      - lang: JavaScript
        label: Node.js
        source: |
          const BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1";
          const res = await fetch(`${BASE}/providers/did:ocss:loopline/connect`);
          console.log(res.status, await res.json());
      - lang: Python
        label: Python
        source: |
          import requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/providers/did:ocss:loopline/connect",
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/providers/did:ocss:loopline/connect", nil)
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: >-
        Returns the OAuth endpoints `@phosra/link` uses to run the connect ceremony
        against a provider. This is the **Phosra product layer** of a two-layer
        resolution — it answers *how* to reach a provider. Whether you *may* connect
        is decided first by the OCSS Trust List (`/.well-known/ocss/trust-list`,
        root-verified): the SDK's resolver requires the DID to be an `active`,
        unexpired entry before this endpoint is ever fetched, and fails closed
        otherwise. Connect configs are deliberately **not** part of the signed OCSS
        document — the standard stays vendor-neutral (trust, keys, accreditation);
        connection mechanics are Phosra's concern as the OCSS implementor.


        Responds `404` (without distinguishing the cases) when the DID is unknown,
        revoked, or accredited but has no connect config yet.
      tags: [Providers]
      security: []
      responses:
        "200":
          description: The provider's OAuth connect config
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProviderConnectConfig"
              example:
                authorize_url: https://api.nextdns.io/oauth2/authorize
                token_url: https://api.nextdns.io/oauth2/token
                profiles_url: https://api.nextdns.io/profiles
                scopes:
                - child_profiles.read
                - policy.write
        "400":
          description: The path segment is not a `did:ocss:` DID
        "404":
          description: Unknown, revoked, or not-yet-configured provider (no existence leak)

        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /platforms/by-capability:
    get:
      operationId: filterPlatformsByCapability
      summary: Filter platforms by capability
      description: "Returns platforms in the catalog that support the given enforcement capability (for example `content_rating` or `screen_time`)."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.platforms.byCapability("web_filtering");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/platforms/by-capability?capability=web_filtering"
      - lang: Python
        label: Python
        source: |
          import requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/platforms/by-capability?capability=web_filtering",
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/platforms/by-capability?capability=web_filtering", nil)
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Platforms]
      security: []
      parameters:
      - name: capability
        in: query
        required: true
        schema:
          type: string
      responses:
        "200":
          description: Filtered platform list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Platform"

              example:
              - id: nextdns
                name: NextDNS
                category: dns
                tier: compliant
                description: DNS-level content filtering and SafeSearch enforcement for family networks.
                icon_url: https://cdn.phosra.com/platforms/nextdns.svg
                auth_type: api_key
                enabled: true
                enforcement_mode: dns
                rule_support: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '502':
          $ref: '#/components/responses/BadGateway'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '500':
          $ref: '#/components/responses/ServerError'
  /platforms/{platformID}/oauth/authorize:
    parameters:
    - name: platformID
      in: path
      required: true
      schema:
        type: string
    get:
      operationId: getOauthAuthorizationUrlForAPlatform
      summary: Get OAuth authorization URL for a platform
      description: "Returns the platform's OAuth 2.0 authorization URL (PKCE S256) to redirect the parent to. Completing the flow links the platform to the family."
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/platforms/fire_tablet/oauth/authorize"
      - lang: JavaScript
        label: Node.js
        source: |
          const BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1";
          const res = await fetch(`${BASE}/platforms/fire_tablet/oauth/authorize`);
          console.log(res.status, await res.json());
      - lang: Python
        label: Python
        source: |
          import requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/platforms/fire_tablet/oauth/authorize",
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/platforms/fire_tablet/oauth/authorize", nil)
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Platforms]
      security: []
      parameters:
      - name: state
        in: query
        schema:
          type: string
      - name: redirect_uri
        in: query
        schema:
          type: string
      responses:
        "200":
          description: Authorization URL
          content:
            application/json:
              schema:
                type: object
                properties:
                  authorize_url:
                    type: string
              example:
                authorize_url: https://api.nextdns.io/oauth2/authorize
        "400":
          description: Platform does not support OAuth

        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '502':
          $ref: '#/components/responses/BadGateway'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '500':
          $ref: '#/components/responses/ServerError'
  /platforms/{platformID}/oauth/callback:
    parameters:
    - name: platformID
      in: path
      required: true
      schema:
        type: string
    get:
      operationId: handleOauthCallback
      summary: Handle OAuth callback
      description: "Completes the platform OAuth flow by exchanging the authorization code returned to the redirect URI, then stores the resulting connection."
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/platforms/fire_tablet/oauth/callback?code=AUTH_CODE_FROM_PROVIDER&redirect_uri=https%3A%2F%2Fyourapp.com%2Foauth%2Fcallback"
      - lang: JavaScript
        label: Node.js
        source: |
          const BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1";
          const res = await fetch(`${BASE}/platforms/fire_tablet/oauth/callback?code=AUTH_CODE_FROM_PROVIDER&redirect_uri=https%3A%2F%2Fyourapp.com%2Foauth%2Fcallback`);
          console.log(res.status, await res.json());
      - lang: Python
        label: Python
        source: |
          import requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/platforms/fire_tablet/oauth/callback?code=AUTH_CODE_FROM_PROVIDER&redirect_uri=https%3A%2F%2Fyourapp.com%2Foauth%2Fcallback",
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/platforms/fire_tablet/oauth/callback?code=AUTH_CODE_FROM_PROVIDER&redirect_uri=https%3A%2F%2Fyourapp.com%2Foauth%2Fcallback", nil)
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Platforms]
      security: []
      parameters:
      - name: code
        in: query
        required: true
        schema:
          type: string
      - name: redirect_uri
        in: query
        schema:
          type: string
      responses:
        "200":
          description: OAuth tokens exchanged
          content:
            application/json:
              schema:
                type: object
                description: Platform-specific OAuth token response
              example: {}
        "400":
          description: Platform does not support OAuth
        "502":
          description: OAuth exchange failed

  # ── Compliance Links ──────────────────────────────────────────────

        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '500':
          $ref: '#/components/responses/ServerError'
  /families/{familyID}/compliance:
    parameters:
    - name: familyID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      operationId: listComplianceLinksForAFamily
      summary: List compliance links for a family
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.compliance.list("f0000000-0000-4000-8000-0000000000f1");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/families/f0000000-0000-4000-8000-0000000000f1/compliance" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/families/f0000000-0000-4000-8000-0000000000f1/compliance",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/families/f0000000-0000-4000-8000-0000000000f1/compliance", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: List all platform connections for a family, showing verification status and last enforcement.
      tags: [Compliance]
      responses:
        "200":
          description: Compliance link list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/ComplianceLink"

              example:
              - id: e1a7c3f9-45b2-4d68-9c01-72f6b8d34a05
                family_id: fcdf4277-ba14-4732-8db5-0369b2c88d8b
                platform_id: nextdns
                status: verified
                external_id: nextdns:profile:a1b2c3
                last_enforcement_at: '2026-06-30T09:15:52Z'
                last_enforcement_status: success
                verified_at: '2026-06-19T08:47:30Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '502':
          $ref: '#/components/responses/BadGateway'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '500':
          $ref: '#/components/responses/ServerError'
  /compliance:
    post:
      operationId: connectAPlatform
      summary: Connect a platform
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.compliance.create({
            family_id: "f0000000-0000-4000-8000-0000000000f1",
            platform_id: "fire_tablet",
            credentials: "oauth_or_manual_token_here"
          });
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X POST "https://phosra-api-sandbox-production.up.railway.app/api/v1/compliance" \
            -H "Authorization: Bearer $PHOSRA_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
            "family_id": "f0000000-0000-4000-8000-0000000000f1",
            "platform_id": "fire_tablet",
            "credentials": "oauth_or_manual_token_here"
          }'
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.post(
              f"{BASE}/compliance",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
              json={
                  "family_id": "f0000000-0000-4000-8000-0000000000f1",
                  "platform_id": "fire_tablet",
                  "credentials": "oauth_or_manual_token_here"
              },
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"bytes"
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	body := bytes.NewBufferString(`{
            "family_id": "f0000000-0000-4000-8000-0000000000f1",
            "platform_id": "fire_tablet",
            "credentials": "oauth_or_manual_token_here"
          }`)
          	req, _ := http.NewRequest("POST", base+"/compliance", body)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	req.Header.Set("Content-Type", "application/json")
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: Connect a platform to the family by providing credentials for verification.
      tags: [Compliance]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [family_id, platform_id, credentials]
              properties:
                family_id:
                  type: string
                  format: uuid
                platform_id:
                  type: string
                credentials:
                  type: string
      responses:
        "201":
          description: Platform connected
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ComplianceLink"

              example:
                id: e1a7c3f9-45b2-4d68-9c01-72f6b8d34a05
                family_id: fcdf4277-ba14-4732-8db5-0369b2c88d8b
                platform_id: nextdns
                status: verified
                external_id: nextdns:profile:a1b2c3
                last_enforcement_at: '2026-06-30T09:15:52Z'
                last_enforcement_status: success
                verified_at: '2026-06-19T08:47:30Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/RateLimited'
        '502':
          $ref: '#/components/responses/BadGateway'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '500':
          $ref: '#/components/responses/ServerError'
  /compliance/{linkID}:
    parameters:
    - name: linkID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    delete:
      operationId: disconnectAPlatform
      summary: Disconnect a platform
      description: "Removes a platform connection so Phosra no longer enforces policies on it. Enforcement already applied on that platform is not rolled back automatically."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          await phosra.compliance.delete("c0ffee00-1234-4567-89ab-cdef01234567");
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X DELETE "https://phosra-api-sandbox-production.up.railway.app/api/v1/compliance/c0ffee00-1234-4567-89ab-cdef01234567" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.delete(
              f"{BASE}/compliance/c0ffee00-1234-4567-89ab-cdef01234567",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("DELETE", base+"/compliance/c0ffee00-1234-4567-89ab-cdef01234567", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Compliance]
      responses:
        "204":
          description: Platform disconnected
        "404":
          description: Compliance link not found

        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '502':
          $ref: '#/components/responses/BadGateway'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '500':
          $ref: '#/components/responses/ServerError'
  /compliance/{linkID}/verify:
    parameters:
    - name: linkID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      operationId: reVerifyAPlatformConnection
      summary: Re-verify a platform connection
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.compliance.verify("c0ffee00-1234-4567-89ab-cdef01234567");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X POST "https://phosra-api-sandbox-production.up.railway.app/api/v1/compliance/c0ffee00-1234-4567-89ab-cdef01234567/verify" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.post(
              f"{BASE}/compliance/c0ffee00-1234-4567-89ab-cdef01234567/verify",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("POST", base+"/compliance/c0ffee00-1234-4567-89ab-cdef01234567/verify", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: Test that credentials still work for an existing platform connection.
      tags: [Compliance]
      responses:
        "200":
          description: Verification result
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
              example:
                status: active
        "502":
          description: Verification failed

        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '500':
          $ref: '#/components/responses/ServerError'
  /compliance/{linkID}/enforce:
    parameters:
    - name: linkID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      operationId: triggerEnforcementForASpecificComplianceLink
      summary: Trigger enforcement for a specific compliance link
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.compliance.enforce("c0ffee00-1234-4567-89ab-cdef01234567");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X POST "https://phosra-api-sandbox-production.up.railway.app/api/v1/compliance/c0ffee00-1234-4567-89ab-cdef01234567/enforce" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.post(
              f"{BASE}/compliance/c0ffee00-1234-4567-89ab-cdef01234567/enforce",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("POST", base+"/compliance/c0ffee00-1234-4567-89ab-cdef01234567/enforce", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: Push the active policy rules to the platform associated with this compliance link.
      tags: [Compliance]
      responses:
        "202":
          description: Enforcement job started
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EnforcementJob"
              example:
                id: b8f04d2e-1c93-4a56-8e7f-05a91d6c3b28
                child_id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                policy_id: 7b3e9c04-2d1a-4f68-9e5b-8c07a1f34d29
                trigger_type: manual
                status: pending
                started_at: '2026-06-30T09:15:44Z'
                completed_at: '2026-06-30T09:15:52Z'
                created_at: '2026-06-18T14:32:07Z'
        "404":
          description: Compliance link not found

  # ── Enforcement ───────────────────────────────────────────────────

        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '502':
          $ref: '#/components/responses/BadGateway'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '500':
          $ref: '#/components/responses/ServerError'
  /children/{childID}/enforce:
    parameters:
    - name: childID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      operationId: triggerEnforcementForAChild
      summary: Trigger enforcement for a child
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.enforcement.trigger("a11ce0fa-0000-4000-8000-0000000000a1");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X POST "https://phosra-api-sandbox-production.up.railway.app/api/v1/children/a11ce0fa-0000-4000-8000-0000000000a1/enforce" \
            -H "Authorization: Bearer $PHOSRA_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
            "platform_ids": [
              "fire_tablet"
            ]
          }'
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.post(
              f"{BASE}/children/a11ce0fa-0000-4000-8000-0000000000a1/enforce",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
              json={
                  "platform_ids": [
                      "fire_tablet"
                  ]
              },
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"bytes"
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	body := bytes.NewBufferString(`{
            "platform_ids": [
              "fire_tablet"
            ]
          }`)
          	req, _ := http.NewRequest("POST", base+"/children/a11ce0fa-0000-4000-8000-0000000000a1/enforce", body)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	req.Header.Set("Content-Type", "application/json")
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: "Push the child's active policy rules to connected platforms. If platform_ids is provided, only those platforms are targeted; otherwise enforcement fans out to ALL connected platforms."
      tags: [Enforcement]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                platform_ids:
                  type: array
                  items:
                    type: string
                  description: "Optional list of platform IDs to target. If omitted, pushes to ALL connected platforms."
      responses:
        "202":
          description: Enforcement job started
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EnforcementJob"

              example:
                id: cf476742-4715-473c-bfac-64d39571ae5c
                child_id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                policy_id: 7b3e9c04-2d1a-4f68-9e5b-8c07a1f34d29
                trigger_type: manual
                status: running
                started_at: '2026-07-06T04:26:01.337741754Z'
                created_at: '2026-07-06T04:26:01.337745101Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '502':
          $ref: '#/components/responses/BadGateway'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '500':
          $ref: '#/components/responses/ServerError'
  /children/{childID}/enforcement/jobs:
    parameters:
    - name: childID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      operationId: listEnforcementJobsForAChild
      summary: List enforcement jobs for a child
      description: "Returns the enforcement jobs run for the child, newest first. Each job fans a policy out to the child's connected platforms."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.enforcement.listJobs("a11ce0fa-0000-4000-8000-0000000000a1");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/children/a11ce0fa-0000-4000-8000-0000000000a1/enforcement/jobs" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/children/a11ce0fa-0000-4000-8000-0000000000a1/enforcement/jobs",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/children/a11ce0fa-0000-4000-8000-0000000000a1/enforcement/jobs", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Enforcement]
      parameters:
      - name: limit
        in: query
        description: Maximum number of jobs to return (default 20)
        schema:
          type: integer
      responses:
        "200":
          description: Enforcement job list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/EnforcementJob"

              example:
              - id: b8f04d2e-1c93-4a56-8e7f-05a91d6c3b28
                child_id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                policy_id: 7b3e9c04-2d1a-4f68-9e5b-8c07a1f34d29
                trigger_type: manual
                status: pending
                started_at: '2026-06-30T09:15:44Z'
                completed_at: '2026-06-30T09:15:52Z'
                created_at: '2026-06-18T14:32:07Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '502':
          $ref: '#/components/responses/BadGateway'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '500':
          $ref: '#/components/responses/ServerError'
  /enforcement/jobs/{jobID}:
    parameters:
    - name: jobID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      operationId: getEnforcementJobStatus
      summary: Get enforcement job status
      description: "Returns the current status of an enforcement job, indicating whether it is queued, running, complete, or failed."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.enforcement.getJob("9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/enforcement/jobs/9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/enforcement/jobs/9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/enforcement/jobs/9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Enforcement]
      responses:
        "200":
          description: Job status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EnforcementJob"
              example:
                id: d4a2a7a9-375e-4c92-950d-214f2ab17262
                child_id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                policy_id: 7b3e9c04-2d1a-4f68-9e5b-8c07a1f34d29
                trigger_type: manual
                status: completed
                started_at: '2026-07-06T04:27:57.030252Z'
                completed_at: '2026-07-06T04:27:57.041987Z'
                created_at: '2026-07-06T04:27:57.030255Z'
        "404":
          description: Job not found

        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '502':
          $ref: '#/components/responses/BadGateway'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '500':
          $ref: '#/components/responses/ServerError'
  /enforcement/jobs/{jobID}/results:
    parameters:
    - name: jobID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      operationId: getEnforcementJobResultsPerPlatform
      summary: Get enforcement job results per platform
      description: "Returns the per-platform outcome of an enforcement job, so you can see which platforms applied the policy and which failed."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.enforcement.getResults("9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/enforcement/jobs/9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6/results" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/enforcement/jobs/9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6/results",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/enforcement/jobs/9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6/results", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Enforcement]
      responses:
        "200":
          description: Per-platform enforcement results
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/EnforcementResult"

              example:
              - id: 5c1e9a37-6b48-42df-90a1-3e8f7c04b2d6
                enforcement_job_id: b8f04d2e-1c93-4a56-8e7f-05a91d6c3b28
                compliance_link_id: e1a7c3f9-45b2-4d68-9c01-72f6b8d34a05
                platform_id: nextdns
                status: pending
                rules_applied: 0
                rules_skipped: 0
                rules_failed: 0
                error_message:
                details: {}
                started_at: '2026-06-30T09:15:44Z'
                completed_at: '2026-06-30T09:15:52Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '502':
          $ref: '#/components/responses/BadGateway'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '500':
          $ref: '#/components/responses/ServerError'
  /enforcement/jobs/{jobID}/retry:
    parameters:
    - name: jobID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      operationId: retryAFailedEnforcementJob
      summary: Retry a failed enforcement job
      description: "Re-runs an enforcement job, retrying only the platforms that previously failed, and returns the new job."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.enforcement.retry("9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X POST "https://phosra-api-sandbox-production.up.railway.app/api/v1/enforcement/jobs/9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6/retry" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.post(
              f"{BASE}/enforcement/jobs/9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6/retry",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("POST", base+"/enforcement/jobs/9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6/retry", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Enforcement]
      responses:
        "202":
          description: Retry job started
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EnforcementJob"
              example:
                id: b8f04d2e-1c93-4a56-8e7f-05a91d6c3b28
                child_id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                policy_id: 7b3e9c04-2d1a-4f68-9e5b-8c07a1f34d29
                trigger_type: manual
                status: pending
                started_at: '2026-06-30T09:15:44Z'
                completed_at: '2026-06-30T09:15:52Z'
                created_at: '2026-06-18T14:32:07Z'
        "404":
          description: Job not found

  # ── Standards / Movements ─────────────────────────────────────────

        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '502':
          $ref: '#/components/responses/BadGateway'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '500':
          $ref: '#/components/responses/ServerError'
  /standards:
    get:
      operationId: listPublishedCommunityStandards
      summary: List published community standards
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.standards.list();
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/standards"
      - lang: Python
        label: Python
        source: |
          import requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/standards",
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/standards", nil)
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: Browse all published community standards/movements. No authentication required.
      tags: [Standards]
      security: []
      responses:
        "200":
          description: Standards list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Standard"

              example:
              - id: f42e8ef8-7534-4cd1-82fa-eff3fbf818e2
                slug: four-norms
                name: Four Norms
                organization: The Anxious Generation
                description: Jonathan Haidt's four foundational norms for a phone-free childhood.
                long_description: Based on The Anxious Generation by Jonathan Haidt, the Four Norms represent a research-backed framework for protecting children from the harms of smartphone-based childhood.
                version: '1.0'
                published: true
                min_age: 0
                max_age: 16
                channel: stable
                source: {}
                co_branding: {}
                sort_order: 0
                adoption_count: 0
                rules:
                - id: 6bc9d15e-31ea-4fce-adf2-a8a8870cc32e
                  standard_id: f42e8ef8-7534-4cd1-82fa-eff3fbf818e2
                  category: social_media_min_age
                  label: No social media until age 16
                  enabled: true
                  config:
                    min_age: 16
                  kind: enforced
                  sort_order: 1
                - id: 904e43dc-ef21-454c-9efc-cac10f8ab897
                  standard_id: f42e8ef8-7534-4cd1-82fa-eff3fbf818e2
                  category: privacy_account_creation
                  label: No smartphones until age 14
                  enabled: true
                  config:
                    min_age: 14
                  kind: enforced
                  sort_order: 2
                - id: ad36b403-a8a0-4697-bb5b-c67c5332e312
                  standard_id: f42e8ef8-7534-4cd1-82fa-eff3fbf818e2
                  category: time_scheduled_hours
                  label: Phone-free schools
                  enabled: true
                  config:
                    blocked_during: school_hours
                  kind: enforced
                  sort_order: 3
                - id: 646309f4-c716-4607-891e-29a73b1e1234
                  standard_id: f42e8ef8-7534-4cd1-82fa-eff3fbf818e2
                  category: time_daily_limit
                  label: More outdoor play
                  enabled: true
                  config:
                    outdoor_minimum_minutes: 120
                  kind: enforced
                  sort_order: 4
                created_at: '2026-06-15T03:04:56.429142Z'
                updated_at: '2026-06-15T03:04:56.429142Z'
              - id: 0890eafb-0a61-46f3-8e9a-21a6e10cf33f
                slug: screen-smart-family
                name: Screen-Smart Family
                organization: Phosra
                description: Phosra's recommended baseline for balanced screen time and online safety.
                long_description: The Screen-Smart Family standard is Phosra's own curated best-practice ruleset, designed by child development experts and informed by current legislation.
                version: '1.0'
                published: true
                min_age: 0
                max_age: 17
                channel: stable
                source: {}
                co_branding: {}
                sort_order: 0
                adoption_count: 0
                rules:
                - id: a01f0637-61a2-4bba-aef4-de7d057377b4
                  standard_id: 0890eafb-0a61-46f3-8e9a-21a6e10cf33f
                  category: time_daily_limit
                  label: Daily screen time
                  enabled: true
                  config:
                    adaptive: true
                  kind: enforced
                  sort_order: 1
                - id: af078ce1-caed-4013-96b9-d7460989c99d
                  standard_id: 0890eafb-0a61-46f3-8e9a-21a6e10cf33f
                  category: content_rating
                  label: Content ratings
                  enabled: true
                  config:
                    level: age_appropriate
                  kind: enforced
                  sort_order: 2
                - id: 22ad722a-6f0d-41bc-a77d-4680b6e1b3af
                  standard_id: 0890eafb-0a61-46f3-8e9a-21a6e10cf33f
                  category: web_safesearch
                  label: Safe search
                  enabled: true
                  config:
                    enforced: true
                  kind: enforced
                  sort_order: 3
                - id: f52fdbb2-56a9-4b9d-8974-11b1cb9ae54c
                  standard_id: 0890eafb-0a61-46f3-8e9a-21a6e10cf33f
                  category: social_chat_control
                  label: Chat controls
                  enabled: true
                  config:
                    mode: known_contacts_only
                  kind: enforced
                  sort_order: 4
                - id: 1e9779a7-848c-452b-92f2-816f3fc519f3
                  standard_id: 0890eafb-0a61-46f3-8e9a-21a6e10cf33f
                  category: purchase_approval
                  label: Purchase approval
                  enabled: true
                  config:
                    required: true
                  kind: enforced
                  sort_order: 5
                - id: 33716301-f55a-4d1b-ae4b-e1494ad59777
                  standard_id: 0890eafb-0a61-46f3-8e9a-21a6e10cf33f
                  category: privacy_data_sharing
                  label: Data sharing
                  enabled: true
                  config:
                    level: minimal
                  kind: enforced
                  sort_order: 6
                created_at: '2026-06-15T03:04:56.429142Z'
                updated_at: '2026-06-15T03:04:56.429142Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /standards/{slug}:
    parameters:
    - name: slug
      in: path
      required: true
      schema:
        type: string
    get:
      operationId: getACommunityStandardBySlug
      summary: Get a community standard by slug
      description: "Returns a single community standard by its slug, including the rule set it bundles."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.standards.get("wait-until-8th");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/standards/wait-until-8th"
      - lang: Python
        label: Python
        source: |
          import requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/standards/wait-until-8th",
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/standards/wait-until-8th", nil)
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Standards]
      security: []
      responses:
        "200":
          description: Standard details including rules
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Standard"
              example:
                id: f0b3d7e2-6a94-4c81-b52f-9d0e1a3c6b48
                slug: four-norms
                name: Four Norms Baseline
                organization: Center for Humane Technology
                description: A community baseline of the four core child-safety protections.
                long_description: "A community baseline covering the four core protections: age-appropriate content ratings, screen-time limits, purchase approval, and private-message restrictions."
                icon_url: https://cdn.phosra.com/platforms/nextdns.svg
                version: "1.2.0"
                published: true
                min_age: 13
                max_age: 0
                adoption_count: 0
                rules:
                - id: f0b3d7e2-6a94-4c81-b52f-9d0e1a3c6b48
                  standard_id: f0b3d7e2-6a94-4c81-b52f-9d0e1a3c6b48
                  category: rating_age_gate
                  label: Enforce age-based content rating
                  enabled: true
                  config: {}
                  sort_order: 0
                created_at: '2026-06-18T14:32:07Z'
                updated_at: '2026-06-30T09:15:44Z'
        "404":
          description: Standard not found

        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /children/{childID}/standards:
    parameters:
    - name: childID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      operationId: listStandardsAdoptedByAChild
      summary: List standards adopted by a child
      description: "Returns the community standards a child has adopted. Adopting a standard applies its bundled rules to the child."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.standards.forChild("a11ce0fa-0000-4000-8000-0000000000a1");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/children/a11ce0fa-0000-4000-8000-0000000000a1/standards" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/children/a11ce0fa-0000-4000-8000-0000000000a1/standards",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/children/a11ce0fa-0000-4000-8000-0000000000a1/standards", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Standards]
      responses:
        "200":
          description: Adopted standards list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Standard"
              example:
              - id: f0b3d7e2-6a94-4c81-b52f-9d0e1a3c6b48
                slug: four-norms
                name: Four Norms Baseline
                organization: Center for Humane Technology
                description: A community baseline of the four core child-safety protections.
                long_description: "A community baseline covering the four core protections: age-appropriate content ratings, screen-time limits, purchase approval, and private-message restrictions."
                icon_url: https://cdn.phosra.com/platforms/nextdns.svg
                version: "1.2.0"
                published: true
                min_age: 13
                max_age: 0
                adoption_count: 0
                rules:
                - id: f0b3d7e2-6a94-4c81-b52f-9d0e1a3c6b48
                  standard_id: f0b3d7e2-6a94-4c81-b52f-9d0e1a3c6b48
                  category: rating_age_gate
                  label: Enforce age-based content rating
                  enabled: true
                  config: {}
                  sort_order: 0
                created_at: '2026-06-18T14:32:07Z'
                updated_at: '2026-06-30T09:15:44Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
    post:
      operationId: adoptAStandardForAChild
      summary: Adopt a standard for a child
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.standards.adopt("a11ce0fa-0000-4000-8000-0000000000a1", {
            standard_id: "2b1c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e"
          });
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X POST "https://phosra-api-sandbox-production.up.railway.app/api/v1/children/a11ce0fa-0000-4000-8000-0000000000a1/standards" \
            -H "Authorization: Bearer $PHOSRA_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
            "standard_id": "2b1c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e"
          }'
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.post(
              f"{BASE}/children/a11ce0fa-0000-4000-8000-0000000000a1/standards",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
              json={
                  "standard_id": "2b1c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e"
              },
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"bytes"
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	body := bytes.NewBufferString(`{
            "standard_id": "2b1c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e"
          }`)
          	req, _ := http.NewRequest("POST", base+"/children/a11ce0fa-0000-4000-8000-0000000000a1/standards", body)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	req.Header.Set("Content-Type", "application/json")
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: Apply a community standard's rules to a child's policy.
      tags: [Standards]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [standard_id]
              properties:
                standard_id:
                  type: string
                  format: uuid
      responses:
        "201":
          description: Standard adopted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StandardAdoption"

              example:
                id: 6b2f8d04-9e17-4a35-8c60-1d9a7e3f0b52
                child_id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                standard_id: f0b3d7e2-6a94-4c81-b52f-9d0e1a3c6b48
                adopted_at: '2026-06-20T11:02:33Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /children/{childID}/standards/{standardID}:
    parameters:
    - name: childID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    - name: standardID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    delete:
      operationId: unadoptAStandardForAChild
      summary: Unadopt a standard for a child
      description: "Removes a previously adopted community standard from the child, withdrawing the rules it contributed."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          await phosra.standards.remove("a11ce0fa-0000-4000-8000-0000000000a1", "2b1c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e");
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X DELETE "https://phosra-api-sandbox-production.up.railway.app/api/v1/children/a11ce0fa-0000-4000-8000-0000000000a1/standards/2b1c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.delete(
              f"{BASE}/children/a11ce0fa-0000-4000-8000-0000000000a1/standards/2b1c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("DELETE", base+"/children/a11ce0fa-0000-4000-8000-0000000000a1/standards/2b1c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Standards]
      responses:
        "204":
          description: Standard unadopted
        "404":
          description: Standard or child not found

  # ── Ratings ───────────────────────────────────────────────────────

        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /ratings/systems:
    get:
      operationId: listRatingSystems
      summary: List rating systems
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.ratings.systems();
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/ratings/systems"
      - lang: Python
        label: Python
        source: |
          import requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/ratings/systems",
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/ratings/systems", nil)
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: "List all content rating systems (MPAA, TV, ESRB, PEGI, CSM)."
      tags: [Ratings]
      security: []
      responses:
        "200":
          description: Rating systems
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/RatingSystem"

              example:
              - id: csm
                name: Common Sense Media
                country: US
                media_type: general
                description: Common Sense Media age-based ratings
              - id: esrb
                name: ESRB
                country: US
                media_type: game
                description: Entertainment Software Rating Board
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /ratings/systems/{systemID}:
    parameters:
    - name: systemID
      in: path
      required: true
      schema:
        type: string
    get:
      operationId: getRatingsForASpecificRatingSystem
      summary: Get ratings for a specific rating system
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.ratings.bySystem("esrb");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/ratings/systems/esrb"
      - lang: Python
        label: Python
        source: |
          import requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/ratings/systems/esrb",
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/ratings/systems/esrb", nil)
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: Returns all individual ratings within the given rating system.
      tags: [Ratings]
      security: []
      responses:
        "200":
          description: Ratings in the system
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Rating"

              example:
              - id: 00000000-0000-0000-0003-000000000002
                system_id: esrb
                code: E10+
                name: Everyone 10+
                description: Content generally suitable for ages 10 and up.
                min_age: 13
                display_order: 0
                restrictive_idx: 0
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /ratings/systems/{systemID}/descriptors:
    parameters:
    - name: systemID
      in: path
      required: true
      schema:
        type: string
    get:
      operationId: getContentDescriptorsForARatingSystem
      summary: Get content descriptors for a rating system
      description: "Returns the content descriptors (such as violence or language) defined by the given rating system."
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/ratings/systems/esrb/descriptors"
      - lang: JavaScript
        label: Node.js
        source: |
          const BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1";
          const res = await fetch(`${BASE}/ratings/systems/esrb/descriptors`);
          console.log(res.status, await res.json());
      - lang: Python
        label: Python
        source: |
          import requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/ratings/systems/esrb/descriptors",
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/ratings/systems/esrb/descriptors", nil)
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Ratings]
      security: []
      responses:
        "200":
          description: Content descriptors
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/ContentDescriptor"

              example:
              - id: 00000000-0000-0000-0003-000000000101
                system_id: esrb
                code: fantasy-violence
                name: Fantasy Violence
                category: violence
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /ratings/by-age:
    get:
      operationId: getRatingsForASpecificAge
      summary: Get ratings for a specific age
      description: "Returns the ratings considered age-appropriate for the supplied age across the supported rating systems."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.ratings.byAge(13);
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/ratings/by-age?age=13"
      - lang: Python
        label: Python
        source: |
          import requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/ratings/by-age?age=13",
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/ratings/by-age?age=13", nil)
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Ratings]
      security: []
      parameters:
      - name: age
        in: query
        required: true
        schema:
          type: integer
      responses:
        "200":
          description: Age-appropriate ratings across all systems
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgeRatings"

              example:
                csm:
                  id: 00000000-0000-0000-0005-000000000004
                  system_id: csm
                  code: 10+
                  name: Age 10+
                  description: Appropriate for ages 10 and up
                  min_age: 10
                  display_order: 4
                  restrictive_idx: 4
                esrb:
                  id: 00000000-0000-0000-0003-000000000002
                  system_id: esrb
                  code: E10+
                  name: Everyone 10+
                  description: Content generally suitable for ages 10 and up
                  min_age: 10
                  display_order: 2
                  restrictive_idx: 2
                mpaa:
                  id: 00000000-0000-0000-0001-000000000002
                  system_id: mpaa
                  code: PG
                  name: Parental Guidance
                  description: Some material may not be suitable for children
                  min_age: 0
                  display_order: 2
                  restrictive_idx: 2
                pegi:
                  id: 00000000-0000-0000-0004-000000000002
                  system_id: pegi
                  code: '7'
                  name: PEGI 7
                  description: Suitable for ages 7 and older
                  min_age: 7
                  display_order: 2
                  restrictive_idx: 2
                tvpg:
                  id: 00000000-0000-0000-0002-000000000004
                  system_id: tvpg
                  code: TV-PG
                  name: Parental Guidance Suggested
                  description: May contain some material parents find unsuitable
                  min_age: 0
                  display_order: 4
                  restrictive_idx: 2
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /ratings/{ratingID}/convert:
    parameters:
    - name: ratingID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      operationId: convertARatingToEquivalentsInOtherSystems
      summary: Convert a rating to equivalents in other systems
      description: "Returns the equivalent ratings in other systems for the given rating (for example mapping an MPAA rating to its PEGI counterpart)."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.ratings.convert("00000000-0000-0000-0003-000000000001");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/ratings/00000000-0000-0000-0003-000000000001/convert"
      - lang: Python
        label: Python
        source: |
          import requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/ratings/00000000-0000-0000-0003-000000000001/convert",
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/ratings/00000000-0000-0000-0003-000000000001/convert", nil)
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Ratings]
      security: []
      responses:
        "200":
          description: Equivalent ratings across systems
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/RatingEquivalence"

  # ── Connections ───────────────────────────────────────────────────

              example:
              - id: 7e4c1a90-3b28-4d67-95f1-0a8c6b2e9d43
                rating_a: 00000000-0000-0000-0003-000000000002
                rating_b: 00000000-0000-0000-0004-000000000002
                strength: 0
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /connections:
    post:
      operationId: connectToAProvider
      summary: Connect to a provider
      description: "Starts a connection to an external provider and returns the created connection. Used to link accounts that sync children or policies into Phosra."
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X POST "https://phosra-api-sandbox-production.up.railway.app/api/v1/connections" \
            -H "Authorization: Bearer $PHOSRA_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
            "family_id": "f0000000-0000-4000-8000-0000000000f1",
            "provider_id": "fire_tablet",
            "credentials": "oauth_or_manual_token_here"
          }'
      - lang: JavaScript
        label: Node.js
        source: |
          const BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1";
          const res = await fetch(`${BASE}/connections`, {
            method: "POST",
            headers: {
              "Authorization": `Bearer ${process.env.PHOSRA_API_KEY}`,
              "Content-Type": "application/json",
            },
            body: JSON.stringify({
              "family_id": "f0000000-0000-4000-8000-0000000000f1",
              "provider_id": "fire_tablet",
              "credentials": "oauth_or_manual_token_here"
            }),
          });
          console.log(res.status, await res.json());
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.post(
              f"{BASE}/connections",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
              json={
                  "family_id": "f0000000-0000-4000-8000-0000000000f1",
                  "provider_id": "fire_tablet",
                  "credentials": "oauth_or_manual_token_here"
              },
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"bytes"
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	body := bytes.NewBufferString(`{
            "family_id": "f0000000-0000-4000-8000-0000000000f1",
            "provider_id": "fire_tablet",
            "credentials": "oauth_or_manual_token_here"
          }`)
          	req, _ := http.NewRequest("POST", base+"/connections", body)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	req.Header.Set("Content-Type", "application/json")
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Connections]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [family_id, provider_id, credentials]
              properties:
                family_id:
                  type: string
                  format: uuid
                provider_id:
                  type: string
                credentials:
                  type: string
      responses:
        "201":
          description: Connected
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProviderConnection"

  # ── Sync ──────────────────────────────────────────────────────────

              example:
                id: 2f9d7b04-6c81-4e35-90a2-5b7e1c3f8d60
                family_id: fcdf4277-ba14-4732-8db5-0369b2c88d8b
                provider_id: nextdns
                status: connected
                last_sync_at: '2026-06-30T09:15:52Z'
                last_sync_status: success
                connected_at: '2026-06-19T08:47:12Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /children/{childID}/sync:
    post:
      operationId: triggerSyncForChildAcrossAllProviders
      summary: Trigger sync for child across all providers
      description: "Queues a sync job that pushes the child's current policies to every connected provider, and returns the created job."
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X POST "https://phosra-api-sandbox-production.up.railway.app/api/v1/children/a11ce0fa-0000-4000-8000-0000000000a1/sync" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: JavaScript
        label: Node.js
        source: |
          const BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1";
          const res = await fetch(`${BASE}/children/a11ce0fa-0000-4000-8000-0000000000a1/sync`, {
            method: "POST",
            headers: {
              "Authorization": `Bearer ${process.env.PHOSRA_API_KEY}`,
            },
          });
          console.log(res.status, await res.json());
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.post(
              f"{BASE}/children/a11ce0fa-0000-4000-8000-0000000000a1/sync",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("POST", base+"/children/a11ce0fa-0000-4000-8000-0000000000a1/sync", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Sync]
      parameters:
      - name: childID
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        "202":
          description: Sync triggered
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SyncJob"

              example:
                id: c93a1e5b-27d6-4f80-91ac-3b6e0d8f7a24
                child_id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                policy_id: 7b3e9c04-2d1a-4f68-9e5b-8c07a1f34d29
                trigger_type: manual
                status: pending
                started_at: '2026-06-30T09:15:44Z'
                completed_at: '2026-06-30T09:15:52Z'
                created_at: '2026-06-18T14:32:07Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '502':
          $ref: '#/components/responses/BadGateway'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '500':
          $ref: '#/components/responses/ServerError'
  /sync/jobs/{jobID}:
    get:
      operationId: getSyncJobStatus
      summary: Get sync job status
      description: "Returns the current status of a sync job, indicating whether it is queued, running, complete, or failed."
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/sync/jobs/9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: JavaScript
        label: Node.js
        source: |
          const BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1";
          const res = await fetch(`${BASE}/sync/jobs/9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6`, {
            headers: {
              "Authorization": `Bearer ${process.env.PHOSRA_API_KEY}`,
            },
          });
          console.log(res.status, await res.json());
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/sync/jobs/9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/sync/jobs/9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Sync]
      parameters:
      - name: jobID
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        "200":
          description: Job status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SyncJob"

              example:
                id: c93a1e5b-27d6-4f80-91ac-3b6e0d8f7a24
                child_id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                policy_id: 7b3e9c04-2d1a-4f68-9e5b-8c07a1f34d29
                trigger_type: manual
                status: pending
                started_at: '2026-06-30T09:15:44Z'
                completed_at: '2026-06-30T09:15:52Z'
                created_at: '2026-06-18T14:32:07Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '502':
          $ref: '#/components/responses/BadGateway'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '500':
          $ref: '#/components/responses/ServerError'
  /sync/jobs/{jobID}/results:
    get:
      operationId: getSyncJobResultsPerProvider
      summary: Get sync job results per provider
      description: "Returns the per-provider outcome of a sync job so you can see which providers accepted the update."
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/sync/jobs/9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6/results" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: JavaScript
        label: Node.js
        source: |
          const BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1";
          const res = await fetch(`${BASE}/sync/jobs/9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6/results`, {
            headers: {
              "Authorization": `Bearer ${process.env.PHOSRA_API_KEY}`,
            },
          });
          console.log(res.status, await res.json());
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/sync/jobs/9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6/results",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/sync/jobs/9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6/results", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Sync]
      parameters:
      - name: jobID
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        "200":
          description: Job results
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/SyncJobResult"

  # ── Webhooks ──────────────────────────────────────────────────────

              example:
              - id: 8a1e6c37-4b90-42d5-81f6-3c0a9e7b2d48
                sync_job_id: c93a1e5b-27d6-4f80-91ac-3b6e0d8f7a24
                provider_id: nextdns
                status: pushed
                rules_applied: 12
                rules_skipped: 3
                rules_failed: 0
                error_message: ""
                details: {}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '502':
          $ref: '#/components/responses/BadGateway'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '500':
          $ref: '#/components/responses/ServerError'
  /families/{familyID}/webhooks:
    parameters:
    - name: familyID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      operationId: listWebhookSubscriptionsForAFamily
      summary: List webhook subscriptions for a family
      description: "Returns the webhook subscriptions registered for the family, including their target URLs and subscribed event types."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.webhooks.list("f0000000-0000-4000-8000-0000000000f1");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/families/f0000000-0000-4000-8000-0000000000f1/webhooks" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/families/f0000000-0000-4000-8000-0000000000f1/webhooks",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/families/f0000000-0000-4000-8000-0000000000f1/webhooks", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Webhooks]
      responses:
        "200":
          description: Webhook list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Webhook"

              example:
              - id: d41c8f2a-90b7-4e63-a15c-8f0e2b7d3946
                family_id: fcdf4277-ba14-4732-8db5-0369b2c88d8b
                url: https://api.rivera-safety.com/webhooks/phosra
                events:
                - policy.updated
                - enforcement.completed
                active: true
                created_at: '2026-06-18T14:32:07Z'
                updated_at: '2026-06-30T09:15:44Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /webhooks:
    post:
      operationId: createAWebhookSubscription
      summary: Create a webhook subscription
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.webhooks.create({
            family_id: "f0000000-0000-4000-8000-0000000000f1",
            url: "https://yourapp.com/webhooks/phosra",
            events: ["enforcement.completed", "policy.updated"]
          });
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X POST "https://phosra-api-sandbox-production.up.railway.app/api/v1/webhooks" \
            -H "Authorization: Bearer $PHOSRA_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
            "family_id": "f0000000-0000-4000-8000-0000000000f1",
            "url": "https://yourapp.com/webhooks/phosra",
            "events": [
              "enforcement.completed",
              "policy.updated"
            ]
          }'
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.post(
              f"{BASE}/webhooks",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
              json={
                  "family_id": "f0000000-0000-4000-8000-0000000000f1",
                  "url": "https://yourapp.com/webhooks/phosra",
                  "events": [
                      "enforcement.completed",
                      "policy.updated"
                  ]
              },
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"bytes"
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	body := bytes.NewBufferString(`{
            "family_id": "f0000000-0000-4000-8000-0000000000f1",
            "url": "https://yourapp.com/webhooks/phosra",
            "events": [
              "enforcement.completed",
              "policy.updated"
            ]
          }`)
          	req, _ := http.NewRequest("POST", base+"/webhooks", body)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	req.Header.Set("Content-Type", "application/json")
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: Register a webhook endpoint to receive event notifications.
      tags: [Webhooks]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [family_id, url, events]
              properties:
                family_id:
                  type: string
                  format: uuid
                url:
                  type: string
                  description: Webhook URL to receive events
                events:
                  type: array
                  items:
                    type: string
                  description: "Events to subscribe to (e.g. policy.updated, enforcement.completed)"
      responses:
        "201":
          description: Webhook created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Webhook"

              example:
                id: d41c8f2a-90b7-4e63-a15c-8f0e2b7d3946
                family_id: fcdf4277-ba14-4732-8db5-0369b2c88d8b
                url: https://api.rivera-safety.com/webhooks/phosra
                events:
                - policy.updated
                - enforcement.completed
                active: true
                created_at: '2026-06-18T14:32:07Z'
                updated_at: '2026-06-30T09:15:44Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /webhooks/{webhookID}:
    parameters:
    - name: webhookID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      operationId: getWebhookDetails
      summary: Get webhook details
      description: "Returns a single webhook subscription by ID, including its target URL, subscribed events, and signing-secret metadata."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.webhooks.get("8a1b2c3d-4e5f-4a6b-9c8d-1e2f3a4b5c6d");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/webhooks/8a1b2c3d-4e5f-4a6b-9c8d-1e2f3a4b5c6d" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/webhooks/8a1b2c3d-4e5f-4a6b-9c8d-1e2f3a4b5c6d",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/webhooks/8a1b2c3d-4e5f-4a6b-9c8d-1e2f3a4b5c6d", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Webhooks]
      responses:
        "200":
          description: Webhook details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Webhook"
              example:
                id: d41c8f2a-90b7-4e63-a15c-8f0e2b7d3946
                family_id: fcdf4277-ba14-4732-8db5-0369b2c88d8b
                url: https://api.rivera-safety.com/webhooks/phosra
                events:
                - policy.updated
                - enforcement.completed
                active: true
                created_at: '2026-06-18T14:32:07Z'
                updated_at: '2026-06-30T09:15:44Z'
        "404":
          description: Webhook not found
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
    put:
      operationId: updateAWebhook
      summary: Update a webhook
      description: "Updates a webhook subscription's target URL or subscribed event types and returns the updated resource."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.webhooks.update("8a1b2c3d-4e5f-4a6b-9c8d-1e2f3a4b5c6d", {
            url: "https://yourapp.com/webhooks/phosra",
            events: ["enforcement.completed"],
            active: true
          });
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X PUT "https://phosra-api-sandbox-production.up.railway.app/api/v1/webhooks/8a1b2c3d-4e5f-4a6b-9c8d-1e2f3a4b5c6d" \
            -H "Authorization: Bearer $PHOSRA_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
            "url": "https://yourapp.com/webhooks/phosra",
            "events": [
              "enforcement.completed"
            ],
            "active": true
          }'
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.put(
              f"{BASE}/webhooks/8a1b2c3d-4e5f-4a6b-9c8d-1e2f3a4b5c6d",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
              json={
                  "url": "https://yourapp.com/webhooks/phosra",
                  "events": [
                      "enforcement.completed"
                  ],
                  "active": True
              },
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"bytes"
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	body := bytes.NewBufferString(`{
            "url": "https://yourapp.com/webhooks/phosra",
            "events": [
              "enforcement.completed"
            ],
            "active": true
          }`)
          	req, _ := http.NewRequest("PUT", base+"/webhooks/8a1b2c3d-4e5f-4a6b-9c8d-1e2f3a4b5c6d", body)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	req.Header.Set("Content-Type", "application/json")
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Webhooks]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                url:
                  type: string
                events:
                  type: array
                  items:
                    type: string
                active:
                  type: boolean
      responses:
        "200":
          description: Webhook updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Webhook"
              example:
                id: d41c8f2a-90b7-4e63-a15c-8f0e2b7d3946
                family_id: fcdf4277-ba14-4732-8db5-0369b2c88d8b
                url: https://api.rivera-safety.com/webhooks/phosra
                events:
                - policy.updated
                - enforcement.completed
                active: true
                created_at: '2026-06-18T14:32:07Z'
                updated_at: '2026-06-30T09:15:44Z'
        "404":
          description: Webhook not found
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
    delete:
      operationId: deleteAWebhook
      summary: Delete a webhook
      description: "Permanently deletes a webhook subscription. Phosra stops delivering events to its target URL immediately."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          await phosra.webhooks.delete("8a1b2c3d-4e5f-4a6b-9c8d-1e2f3a4b5c6d");
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X DELETE "https://phosra-api-sandbox-production.up.railway.app/api/v1/webhooks/8a1b2c3d-4e5f-4a6b-9c8d-1e2f3a4b5c6d" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.delete(
              f"{BASE}/webhooks/8a1b2c3d-4e5f-4a6b-9c8d-1e2f3a4b5c6d",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("DELETE", base+"/webhooks/8a1b2c3d-4e5f-4a6b-9c8d-1e2f3a4b5c6d", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Webhooks]
      responses:
        "204":
          description: Webhook deleted
        "404":
          description: Webhook not found

        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /webhooks/{webhookID}/test:
    parameters:
    - name: webhookID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      operationId: sendATestDeliveryToAWebhook
      summary: Send a test delivery to a webhook
      description: "Sends a sample event to the webhook's target URL so you can verify signature validation and connectivity, and returns the delivery result."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.webhooks.test("8a1b2c3d-4e5f-4a6b-9c8d-1e2f3a4b5c6d");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X POST "https://phosra-api-sandbox-production.up.railway.app/api/v1/webhooks/8a1b2c3d-4e5f-4a6b-9c8d-1e2f3a4b5c6d/test" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.post(
              f"{BASE}/webhooks/8a1b2c3d-4e5f-4a6b-9c8d-1e2f3a4b5c6d/test",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("POST", base+"/webhooks/8a1b2c3d-4e5f-4a6b-9c8d-1e2f3a4b5c6d/test", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Webhooks]
      responses:
        "200":
          description: Test delivery result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookDelivery"
              example:
                id: 1c7e9a34-2d68-4b05-93f1-6a0c8e4d7b29
                webhook_id: d41c8f2a-90b7-4e63-a15c-8f0e2b7d3946
                event: policy.updated
                payload: {}
                response_code: 0
                success: true
                attempts: 0
                next_retry_at: '2026-07-06T04:40:00Z'
                created_at: '2026-06-18T14:32:07Z'
        "404":
          description: Webhook not found

        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /webhooks/{webhookID}/deliveries:
    parameters:
    - name: webhookID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      operationId: listWebhookDeliveries
      summary: List webhook deliveries
      description: "Returns recent delivery attempts for the webhook, including response status codes, so you can debug failures and retries."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.webhooks.deliveries("8a1b2c3d-4e5f-4a6b-9c8d-1e2f3a4b5c6d");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/webhooks/8a1b2c3d-4e5f-4a6b-9c8d-1e2f3a4b5c6d/deliveries" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/webhooks/8a1b2c3d-4e5f-4a6b-9c8d-1e2f3a4b5c6d/deliveries",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/webhooks/8a1b2c3d-4e5f-4a6b-9c8d-1e2f3a4b5c6d/deliveries", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Webhooks]
      parameters:
      - name: limit
        in: query
        description: Maximum number of deliveries to return (default 50)
        schema:
          type: integer
      responses:
        "200":
          description: Delivery history
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/WebhookDelivery"

  # ── Reports ───────────────────────────────────────────────────────

              example:
              - id: 1c7e9a34-2d68-4b05-93f1-6a0c8e4d7b29
                webhook_id: d41c8f2a-90b7-4e63-a15c-8f0e2b7d3946
                event: policy.updated
                payload: {}
                response_code: 0
                success: true
                attempts: 0
                next_retry_at: '2026-07-06T04:40:00Z'
                created_at: '2026-06-18T14:32:07Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /families/{familyID}/reports/overview:
    parameters:
    - name: familyID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      operationId: familyOverviewReport
      summary: Family overview report
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.reports.familyOverview("f0000000-0000-4000-8000-0000000000f1");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/families/f0000000-0000-4000-8000-0000000000f1/reports/overview" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/families/f0000000-0000-4000-8000-0000000000f1/reports/overview",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/families/f0000000-0000-4000-8000-0000000000f1/reports/overview", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: "Get a health dashboard for a family: children, active policies, enforcement status, and recommendations."
      tags: [Reports]
      responses:
        "200":
          description: Family overview
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FamilyOverview"
              example:
                children:
                - child:
                    id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                    family_id: fcdf4277-ba14-4732-8db5-0369b2c88d8b
                    name: Ada
                    birth_date: '2015-04-10T00:00:00Z'
                    avatar_url: https://cdn.phosra.com/avatars/ada.png
                    created_at: '2026-06-18T14:32:07Z'
                  active_policies: 0
                  last_enforcement_at: '2026-06-30T09:15:52Z'
                  enforcement_status: healthy
                total_platforms: 0
                enforcement_health: healthy
                recent_enforcements:
                - id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                  child_id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                  policy_id: 7b3e9c04-2d1a-4f68-9e5b-8c07a1f34d29
                  trigger_type: manual
                  status: pending
                  started_at: '2026-06-30T09:15:44Z'
                  completed_at: '2026-06-30T09:15:52Z'
                  created_at: '2026-06-18T14:32:07Z'
        "404":
          description: Family not found

  # ── Feedback ──────────────────────────────────────────────────────

        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /feedback:
    post:
      operationId: submitUiFeedback
      summary: Submit UI feedback
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X POST "https://phosra-api-sandbox-production.up.railway.app/api/v1/feedback" \
            -H "Content-Type: application/json" \
            -d '{
            "page_route": "/dashboard",
            "comment": "The save button overlaps the header on mobile.",
            "reviewer_name": "Ada"
          }'
      - lang: JavaScript
        label: Node.js
        source: |
          const BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1";
          const res = await fetch(`${BASE}/feedback`, {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify({
              "page_route": "/dashboard",
              "comment": "The save button overlaps the header on mobile.",
              "reviewer_name": "Ada"
            }),
          });
          console.log(res.status, await res.json());
      - lang: Python
        label: Python
        source: |
          import requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.post(
              f"{BASE}/feedback",
              json={
                  "page_route": "/dashboard",
                  "comment": "The save button overlaps the header on mobile.",
                  "reviewer_name": "Ada"
              },
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"bytes"
          	"fmt"
          	"io"
          	"net/http"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	body := bytes.NewBufferString(`{
            "page_route": "/dashboard",
            "comment": "The save button overlaps the header on mobile.",
            "reviewer_name": "Ada"
          }`)
          	req, _ := http.NewRequest("POST", base+"/feedback", body)
          	req.Header.Set("Content-Type", "application/json")
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: Submit visual feedback from reviewers. No authentication required.
      tags: [Feedback]
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [page_route, comment]
              properties:
                page_route:
                  type: string
                css_selector:
                  type: string
                component_hint:
                  type: string
                comment:
                  type: string
                reviewer_name:
                  type: string
                viewport_width:
                  type: integer
                viewport_height:
                  type: integer
                click_x:
                  type: integer
                click_y:
                  type: integer
      responses:
        "201":
          description: Feedback submitted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UIFeedback"
              example:
                id: 9d2b6f81-4a07-4c53-8e19-0f7a3c6b2d84
                page_route: /dashboard/children
                css_selector: button.add-child
                component_hint: AddChildButton
                comment: The add-child button overlaps the header on iPhone SE
                reviewer_name: Jordan Lee
                status: open
                viewport_width: 0
                viewport_height: 0
                click_x: 0
                click_y: 0
                created_at: '2026-06-18T14:32:07Z'
                resolved_at: '2026-06-28T16:20:05Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
    get:
      operationId: listFeedbackItems
      summary: List feedback items
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/feedback"
      - lang: JavaScript
        label: Node.js
        source: |
          const BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1";
          const res = await fetch(`${BASE}/feedback`);
          console.log(res.status, await res.json());
      - lang: Python
        label: Python
        source: |
          import requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/feedback",
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/feedback", nil)
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: List all feedback items, optionally filtered by status. No authentication required.
      tags: [Feedback]
      security: []
      parameters:
      - name: status
        in: query
        description: Filter by status (open, approved, dismissed, fixed)
        schema:
          type: string
          enum: [open, approved, dismissed, fixed]
      responses:
        "200":
          description: Feedback list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/UIFeedback"

              example:
              - id: 9d2b6f81-4a07-4c53-8e19-0f7a3c6b2d84
                page_route: /dashboard/children
                css_selector: button.add-child
                component_hint: AddChildButton
                comment: The add-child button overlaps the header on iPhone SE
                reviewer_name: Jordan Lee
                status: open
                viewport_width: 0
                viewport_height: 0
                click_x: 0
                click_y: 0
                created_at: '2026-06-18T14:32:07Z'
                resolved_at: '2026-06-28T16:20:05Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /feedback/{feedbackID}/status:
    parameters:
    - name: feedbackID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    patch:
      operationId: updateFeedbackStatus
      summary: Update feedback status
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X PATCH "https://phosra-api-sandbox-production.up.railway.app/api/v1/feedback/6f1a2b3c-4d5e-4f6a-8b9c-0d1e2f3a4b5c/status" \
            -H "Authorization: Bearer $PHOSRA_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
            "status": "approved"
          }'
      - lang: JavaScript
        label: Node.js
        source: |
          const BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1";
          const res = await fetch(`${BASE}/feedback/6f1a2b3c-4d5e-4f6a-8b9c-0d1e2f3a4b5c/status`, {
            method: "PATCH",
            headers: {
              "Authorization": `Bearer ${process.env.PHOSRA_API_KEY}`,
              "Content-Type": "application/json",
            },
            body: JSON.stringify({
              "status": "approved"
            }),
          });
          console.log(res.status, await res.json());
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.patch(
              f"{BASE}/feedback/6f1a2b3c-4d5e-4f6a-8b9c-0d1e2f3a4b5c/status",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
              json={
                  "status": "approved"
              },
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"bytes"
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	body := bytes.NewBufferString(`{
            "status": "approved"
          }`)
          	req, _ := http.NewRequest("PATCH", base+"/feedback/6f1a2b3c-4d5e-4f6a-8b9c-0d1e2f3a4b5c/status", body)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	req.Header.Set("Content-Type", "application/json")
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: Update the status of a feedback item. Requires authentication.
      tags: [Feedback]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [status]
              properties:
                status:
                  type: string
                  enum: [open, approved, dismissed, fixed]
      responses:
        "200":
          description: Status updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string

  # ── Apple Device Sync ─────────────────────────────────────────────

              example:
                status: active
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /children/{childID}/devices:
    parameters:
    - name: childID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      operationId: registerAnAppleDevice
      summary: Register an Apple device
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.devices.register("a11ce0fa-0000-4000-8000-0000000000a1", {
            device_name: "Mia's iPhone",
            device_model: "iPhone15,2",
            os_version: "18.5",
            app_version: "1.4.0"
          });
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X POST "https://phosra-api-sandbox-production.up.railway.app/api/v1/children/a11ce0fa-0000-4000-8000-0000000000a1/devices" \
            -H "Authorization: Bearer $PHOSRA_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
            "device_name": "Mia's iPhone",
            "device_model": "iPhone15,2",
            "os_version": "18.5",
            "app_version": "1.4.0"
          }'
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.post(
              f"{BASE}/children/a11ce0fa-0000-4000-8000-0000000000a1/devices",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
              json={
                  "device_name": "Mia's iPhone",
                  "device_model": "iPhone15,2",
                  "os_version": "18.5",
                  "app_version": "1.4.0"
              },
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"bytes"
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	body := bytes.NewBufferString(`{
            "device_name": "Mia's iPhone",
            "device_model": "iPhone15,2",
            "os_version": "18.5",
            "app_version": "1.4.0"
          }`)
          	req, _ := http.NewRequest("POST", base+"/children/a11ce0fa-0000-4000-8000-0000000000a1/devices", body)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	req.Header.Set("Content-Type", "application/json")
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Apple Device Sync]
      description: Register a device for on-device policy enforcement. Returns a one-time API key the iOS app stores in Keychain.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RegisterDeviceRequest"
      responses:
        "201":
          description: Device registered
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RegisterDeviceResponse"
              example:
                device:
                  id: 5e0a9c37-1b64-4d82-96f3-2c8a7e0b4d19
                  child_id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                  family_id: fcdf4277-ba14-4732-8db5-0369b2c88d8b
                  platform_id: nextdns
                  device_name: "Ada's iPhone"
                  device_model: iPhone15,3
                  os_version: "18.5"
                  app_version: "2.4.0"
                  apns_token: 8f7d3c1b2a90e64f5d8c7b6a1f0e9d3c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6
                  capabilities:
                  - FamilyControls
                  - ManagedSettings
                  - DeviceActivity
                  enforcement_summary: {}
                  last_seen_at: '2026-07-05T22:04:18Z'
                  last_policy_version: 0
                  status: active
                  created_at: '2026-06-18T14:32:07Z'
                  updated_at: '2026-06-30T09:15:44Z'
                api_key: dvk_test_3a9f1c7e5b2d8046a1f3c9e7b5d2086
        "404":
          description: Child not found
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
    get:
      operationId: listRegisteredDevicesForAChild
      summary: List registered devices for a child
      description: "Returns the Apple devices registered for the child via Apple Device Sync, including their enrollment status."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.devices.list("a11ce0fa-0000-4000-8000-0000000000a1");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/children/a11ce0fa-0000-4000-8000-0000000000a1/devices" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/children/a11ce0fa-0000-4000-8000-0000000000a1/devices",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/children/a11ce0fa-0000-4000-8000-0000000000a1/devices", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Apple Device Sync]
      responses:
        "200":
          description: Device list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/DeviceRegistration"

              example:
              - id: 5e0a9c37-1b64-4d82-96f3-2c8a7e0b4d19
                child_id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                family_id: fcdf4277-ba14-4732-8db5-0369b2c88d8b
                platform_id: nextdns
                device_name: "Ada's iPhone"
                device_model: iPhone15,3
                os_version: "18.5"
                app_version: "2.4.0"
                apns_token: 8f7d3c1b2a90e64f5d8c7b6a1f0e9d3c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6
                capabilities:
                - FamilyControls
                - ManagedSettings
                - DeviceActivity
                enforcement_summary: {}
                last_seen_at: '2026-07-05T22:04:18Z'
                last_policy_version: 0
                status: active
                created_at: '2026-06-18T14:32:07Z'
                updated_at: '2026-06-30T09:15:44Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /devices/{deviceID}:
    parameters:
    - name: deviceID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    put:
      operationId: updateDeviceMetadata
      summary: Update device metadata
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.devices.update("d1e2f3a4-5b6c-4d7e-8f90-a1b2c3d4e5f6", {
            device_name: "Mia's iPhone",
            app_version: "1.5.0",
            os_version: "18.6"
          });
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X PUT "https://phosra-api-sandbox-production.up.railway.app/api/v1/devices/d1e2f3a4-5b6c-4d7e-8f90-a1b2c3d4e5f6" \
            -H "Authorization: Bearer $PHOSRA_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
            "device_name": "Mia's iPhone",
            "app_version": "1.5.0",
            "os_version": "18.6"
          }'
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.put(
              f"{BASE}/devices/d1e2f3a4-5b6c-4d7e-8f90-a1b2c3d4e5f6",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
              json={
                  "device_name": "Mia's iPhone",
                  "app_version": "1.5.0",
                  "os_version": "18.6"
              },
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"bytes"
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	body := bytes.NewBufferString(`{
            "device_name": "Mia's iPhone",
            "app_version": "1.5.0",
            "os_version": "18.6"
          }`)
          	req, _ := http.NewRequest("PUT", base+"/devices/d1e2f3a4-5b6c-4d7e-8f90-a1b2c3d4e5f6", body)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	req.Header.Set("Content-Type", "application/json")
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Apple Device Sync]
      description: Update APNs token, app version, or device name. All fields are optional.
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateDeviceRequest"
      responses:
        "200":
          description: Device updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DeviceRegistration"
              example:
                id: 5e0a9c37-1b64-4d82-96f3-2c8a7e0b4d19
                child_id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                family_id: fcdf4277-ba14-4732-8db5-0369b2c88d8b
                platform_id: nextdns
                device_name: "Ada's iPhone"
                device_model: iPhone15,3
                os_version: "18.5"
                app_version: "2.4.0"
                apns_token: 8f7d3c1b2a90e64f5d8c7b6a1f0e9d3c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6
                capabilities:
                - FamilyControls
                - ManagedSettings
                - DeviceActivity
                enforcement_summary: {}
                last_seen_at: '2026-07-05T22:04:18Z'
                last_policy_version: 0
                status: active
                created_at: '2026-06-18T14:32:07Z'
                updated_at: '2026-06-30T09:15:44Z'
        "404":
          description: Device not found
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
    delete:
      operationId: revokeADevice
      summary: Revoke a device
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          await phosra.devices.revoke("d1e2f3a4-5b6c-4d7e-8f90-a1b2c3d4e5f6");
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X DELETE "https://phosra-api-sandbox-production.up.railway.app/api/v1/devices/d1e2f3a4-5b6c-4d7e-8f90-a1b2c3d4e5f6" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.delete(
              f"{BASE}/devices/d1e2f3a4-5b6c-4d7e-8f90-a1b2c3d4e5f6",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("DELETE", base+"/devices/d1e2f3a4-5b6c-4d7e-8f90-a1b2c3d4e5f6", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Apple Device Sync]
      description: Revoke the device's API key. The device will receive 401 on its next request.
      responses:
        "204":
          description: Device revoked

        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /device/policy:
    get:
      operationId: getCompiledPolicyForDevice
      summary: Get compiled policy for device
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.devices.getPolicy();
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/device/policy" \
            -H "X-Device-Key: $PHOSRA_DEVICE_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/device/policy",
              headers={"X-Device-Key": f"{os.environ['PHOSRA_DEVICE_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/device/policy", nil)
          	req.Header.Set("X-Device-Key", os.Getenv("PHOSRA_DEVICE_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Apple Device Sync]
      security:
      - DeviceKeyAuth: []
      description: Returns a structured policy document the iOS app interprets to configure FamilyControls/ManagedSettings. Supports conditional polling via since_version query parameter.
      parameters:
      - name: since_version
        in: query
        description: Return 304 if policy version is <= this value
        schema:
          type: integer
      responses:
        "200":
          description: Compiled policy document
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CompiledPolicy"
              example:
                version: 1
                child_id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                child_age: 0
                age_group: elementary
                policy_id: 7b3e9c04-2d1a-4f68-9e5b-8c07a1f34d29
                status: active
                generated_at: '2026-06-30T09:15:44Z'
                content_filter:
                  age_rating: PG
                  max_ratings: {}
                  blocked_apps:
                  - com.tiktok.app
                  - com.snapchat.snapchat
                  allowed_apps:
                  - com.apple.mobilesafari
                  - com.duolingo.DuolingoMobile
                  allowlist_mode: true
                screen_time:
                  daily_limit_minutes: 0
                  per_app_limits:
                  - bundle_id: 9f8b2a14-6c3d-4e51-8a72-1f0b9c4d2e63
                    daily_minutes: 0
                  downtime_windows:
                  - days_of_week:
                    - monday
                    - tuesday
                    - wednesday
                    - thursday
                    - friday
                    start_time: "22:00"
                    end_time: "07:00"
                  always_allowed_apps:
                  - com.apple.MobileSMS
                  - com.apple.mobilephone
                  schedule:
                    weekday:
                      start: "07:00"
                      end: "21:00"
                    weekend:
                      start: "07:00"
                      end: "21:00"
                purchases:
                  require_approval: true
                  block_iap: true
                  spending_cap_usd: 0
                privacy:
                  location_sharing_enabled: true
                  profile_visibility: friends_only
                  account_creation_approval: true
                  data_sharing_restricted: true
                social:
                  chat_mode: restricted
                  dm_restriction: contacts_only
                  multiplayer_mode: friends_only
                notifications:
                  curfew_start: "21:00"
                  curfew_end: "07:00"
                  usage_timer_minutes: 0
                web_filter:
                  level: moderate
                  safe_search: true
                  blocked_domains:
                  - tiktok.com
                  - reddit.com
                  allowed_domains:
                  - khanacademy.org
                  - wikipedia.org
                  blocked_categories:
                  - gambling
                  - adult
        "304":
          description: Policy unchanged since the given version
        "401":
          description: Invalid or revoked device key

        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /device/report:
    post:
      operationId: submitActivityReportFromDevice
      summary: Submit activity report from device
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.devices.submitReport({
            report_type: "screen_time",
            payload: {
              minutes_used: 74,
              top_app: "com.example.game"
            },
            reported_at: "2026-07-06T14:30:00Z"
          });
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X POST "https://phosra-api-sandbox-production.up.railway.app/api/v1/device/report" \
            -H "X-Device-Key: $PHOSRA_DEVICE_KEY" \
            -H "Content-Type: application/json" \
            -d '{
            "report_type": "screen_time",
            "payload": {
              "minutes_used": 74,
              "top_app": "com.example.game"
            },
            "reported_at": "2026-07-06T14:30:00Z"
          }'
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.post(
              f"{BASE}/device/report",
              headers={"X-Device-Key": f"{os.environ['PHOSRA_DEVICE_KEY']}"},
              json={
                  "report_type": "screen_time",
                  "payload": {
                      "minutes_used": 74,
                      "top_app": "com.example.game"
                  },
                  "reported_at": "2026-07-06T14:30:00Z"
              },
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"bytes"
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	body := bytes.NewBufferString(`{
            "report_type": "screen_time",
            "payload": {
              "minutes_used": 74,
              "top_app": "com.example.game"
            },
            "reported_at": "2026-07-06T14:30:00Z"
          }`)
          	req, _ := http.NewRequest("POST", base+"/device/report", body)
          	req.Header.Set("X-Device-Key", os.Getenv("PHOSRA_DEVICE_KEY"))
          	req.Header.Set("Content-Type", "application/json")
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Apple Device Sync]
      security:
      - DeviceKeyAuth: []
      description: "Submit screen time, app usage, or other activity data. Stored in device_reports and fanned out to activity_logs. Use report_type: enforcement_status with an EnforcementStatusPayload to report per-category enforcement results; this also updates the device's enforcement_summary for the parent dashboard."
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DeviceReportRequest"
      responses:
        "202":
          description: Report accepted
        "401":
          description: Invalid or revoked device key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: Unauthorized
                message: "missing X-Device-Key header"
                code: 401

        '400':
          $ref: '#/components/responses/BadRequest'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /device/ack:
    post:
      operationId: acknowledgePolicyVersion
      summary: Acknowledge policy version
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          await phosra.devices.ack({
            version: 3
          });
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X POST "https://phosra-api-sandbox-production.up.railway.app/api/v1/device/ack" \
            -H "X-Device-Key: $PHOSRA_DEVICE_KEY" \
            -H "Content-Type: application/json" \
            -d '{
            "version": 3
          }'
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.post(
              f"{BASE}/device/ack",
              headers={"X-Device-Key": f"{os.environ['PHOSRA_DEVICE_KEY']}"},
              json={
                  "version": 3
              },
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"bytes"
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	body := bytes.NewBufferString(`{
            "version": 3
          }`)
          	req, _ := http.NewRequest("POST", base+"/device/ack", body)
          	req.Header.Set("X-Device-Key", os.Getenv("PHOSRA_DEVICE_KEY"))
          	req.Header.Set("Content-Type", "application/json")
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Apple Device Sync]
      security:
      - DeviceKeyAuth: []
      description: Confirm that the device has successfully applied a specific policy version.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [version]
              properties:
                version:
                  type: integer
      responses:
        "200":
          description: Version acknowledged
          content:
            application/json:
              schema:
                type: object
                properties:
                  acknowledged_version:
                    type: integer
              example:
                acknowledged_version: 0
        "401":
          description: Invalid or revoked device key

        '400':
          $ref: '#/components/responses/BadRequest'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /platform-mappings/{platformID}:
    parameters:
    - name: platformID
      in: path
      required: true
      schema:
        type: string
        enum: [apple, android]
    get:
      operationId: getPlatformSpecificIdentifierMappings
      summary: Get platform-specific identifier mappings
      x-codeSamples:
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/platform-mappings/fire_tablet"
      - lang: JavaScript
        label: Node.js
        source: |
          const BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1";
          const res = await fetch(`${BASE}/platform-mappings/fire_tablet`);
          console.log(res.status, await res.json());
      - lang: Python
        label: Python
        source: |
          import requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/platform-mappings/fire_tablet",
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/platform-mappings/fire_tablet", nil)
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Device Sync]
      security: []
      description: "Returns platform-specific app identifiers, age ratings, system apps, always-allowed apps, and category_frameworks mapping (which framework + API class to use for each Phosra rule category). Supports platformID=apple and platformID=android."
      responses:
        "200":
          description: Platform mappings
          content:
            application/json:
              schema:
                oneOf:
                - $ref: "#/components/schemas/ApplePlatformMappings"
                - $ref: "#/components/schemas/AndroidPlatformMappings"
              examples:
                apple:
                  summary: "iOS / iPadOS (GET /platform-mappings/apple) — real sandbox capture"
                  value: {"age_ratings": {"AO": "17+", "E": "4+", "E10+": "9+", "G": "4+", "M": "17+", "NC-17": "17+", "PG": "9+", "PG-13": "12+", "R": "17+", "T": "12+", "TV-14": "12+", "TV-G": "4+", "TV-MA": "17+", "TV-PG": "9+", "TV-Y": "4+", "TV-Y7": "4+"}, "app_categories": {"dating": {"bundle_ids": ["com.cardify.tinder", "com.bumble.app", "com.coffeemeetsbagel.app"]}, "gambling": {"bundle_ids": ["com.draftkings.sportsbook", "com.fanduel.sportsbook"], "web_domains": ["draftkings.com", "fanduel.com", "betmgm.com"]}, "gaming": {"app_store_category": "Games", "bundle_ids": []}, "messaging": {"bundle_ids": ["net.whatsapp.WhatsApp", "org.telegram.TelegramEnterprise", "com.facebook.Messenger", "com.discord"]}, "social-media": {"app_store_category": "SNS", "bundle_ids": ["com.burbn.instagram", "com.atebits.Tweetie2", "com.toyopagroup.picaboo", "com.zhiliaoapp.musically", "com.facebook.Facebook"]}, "video-streaming": {"bundle_ids": ["com.google.ios.youtube", "com.netflix.Netflix", "com.disney.disneyplus", "com.amazon.aiv.AIVApp"]}}, "system_apps": {"camera": "com.apple.camera", "clock": "com.apple.mobiletimer", "facetime": "com.apple.facetime", "find_my": "com.apple.findmy", "health": "com.apple.Health", "maps": "com.apple.Maps", "messages": "com.apple.MobileSMS", "phone": "com.apple.mobilephone", "photos": "com.apple.mobileslideshow", "settings": "com.apple.Preferences", "wallet": "com.apple.Passbook"}, "always_allowed": ["com.apple.mobilephone", "com.apple.MobileSMS", "com.apple.Maps", "com.apple.Health", "com.apple.findmy"], "category_frameworks": {"addictive_design_control": {"framework": "none", "api_class": "", "min_os": "", "notes": "Platform-level — no Apple API; can block offending apps via ManagedSettings"}, "age_gate": {"framework": "none", "api_class": "", "min_os": "", "notes": "Enforce in-app; Apple doesn't expose age verification APIs"}, "ai_minor_interaction": {"framework": "none", "api_class": "", "min_os": "", "notes": "Platform-level — restrict AI apps via ManagedSettings blocks"}, "algo_feed_control": {"framework": "none", "api_class": "", "min_os": "", "notes": "Platform-level — no Apple API; enforce by blocking/limiting social apps"}, "algorithmic_audit": {"framework": "none", "api_class": "", "min_os": "", "notes": "Platform transparency requirement — no Apple enforcement API"}, "commercial_data_ban": {"framework": "none", "api_class": "", "min_os": "", "notes": "App/server-level policy — no Apple API"}, "content_allow_title": {"framework": "ManagedSettings", "api_class": "ManagedSettingsStore.application.denyAppInstallation", "min_os": "16.0", "notes": "Invert to allowlist with denyAppInstallation=true + exceptions"}, "content_allowlist_mode": {"framework": "ManagedSettings", "api_class": "ManagedSettingsStore.application.denyAppInstallation", "min_os": "16.0", "notes": "Set denyAppInstallation=true then allowlist specific tokens"}, "content_block_title": {"framework": "ManagedSettings", "api_class": "ManagedSettingsStore.application.blockedApplications", "min_os": "16.0"}, "content_descriptor_block": {"framework": "ManagedSettings", "api_class": "ManagedSettingsStore.application", "min_os": "16.0", "notes": "Use content rating descriptors to filter"}, "content_rating": {"framework": "ManagedSettings", "api_class": "ManagedSettingsStore.application", "min_os": "16.0", "notes": "Filter by App Store age rating via ApplicationToken"}, "csam_reporting": {"framework": "none", "api_class": "", "min_os": "", "notes": "Apple handles CSAM detection server-side in iCloud Photos"}, "data_deletion_request": {"framework": "none", "api_class": "", "min_os": "", "notes": "App-level responsibility; submit via in-app support flow"}, "dm_restriction": {"framework": "none", "api_class": "", "min_os": "", "notes": "Block messaging apps via ManagedSettings as a proxy"}, "geolocation_opt_in": {"framework": "none", "api_class": "", "min_os": "", "notes": "CLLocationManager authorization is per-app; no blanket toggle"}, "image_rights_minor": {"framework": "none", "api_class": "", "min_os": "", "notes": "App-level enforcement
                          — no Apple framework controls this"}, "library_filter_compliance": {"framework": "ManagedSettings", "api_class": "ManagedSettingsStore.webContent", "min_os": "16.0", "notes": "Same as web content filtering — satisfies CIPA-style requirements"}, "monitoring_activity": {"framework": "DeviceActivity", "api_class": "DeviceActivityReport", "min_os": "16.0", "notes": "SwiftUI extension — renders usage reports in the parent app"}, "monitoring_alerts": {"framework": "DeviceActivity", "api_class": "DeviceActivityMonitor", "min_os": "16.0", "notes": "DeviceActivityMonitor.events fires when thresholds are crossed"}, "notification_curfew": {"framework": "ManagedSettings", "api_class": "ManagedSettingsStore.notification", "min_os": "16.4", "notes": "Silence notifications from managed apps during curfew"}, "parental_consent_gate": {"framework": "FamilyControls", "api_class": "AuthorizationCenter", "min_os": "16.0", "notes": "FamilyControls authorization acts as verifiable parental consent"}, "parental_event_notification": {"framework": "DeviceActivity", "api_class": "DeviceActivityMonitor", "min_os": "16.0", "notes": "Push parent notification when flagged events fire"}, "privacy_account_creation": {"framework": "none", "api_class": "", "min_os": "", "notes": "Gate in-app via FamilyControls authorization check"}, "privacy_data_sharing": {"framework": "none", "api_class": "", "min_os": "", "notes": "Enforce via app-level logic; Apple's App Tracking Transparency covers ads only"}, "privacy_location": {"framework": "none", "api_class": "", "min_os": "", "notes": "Location Services controlled at OS level via Settings; use CLLocationManager delegation in-app"}, "privacy_profile_visibility": {"framework": "none", "api_class": "", "min_os": "", "notes": "App-level concern — no Apple framework controls profile visibility"}, "purchase_approval": {"framework": "ManagedSettings", "api_class": "ManagedSettingsStore.appStore.requirePasswordForPurchases", "min_os": "16.0"}, "purchase_block_iap": {"framework": "ManagedSettings", "api_class": "ManagedSettingsStore.appStore.denyInAppPurchases", "min_os": "16.0"}, "purchase_spending_cap": {"framework": "none", "api_class": "", "min_os": "", "notes": "No Apple API — track via StoreKit transaction observer"}, "screen_time_report": {"framework": "DeviceActivity", "api_class": "DeviceActivityReport", "min_os": "16.0", "notes": "SwiftUI DeviceActivityReport extension generates usage summaries"}, "social_chat_control": {"framework": "none", "api_class": "", "min_os": "", "notes": "No direct Apple API — enforce via app-level ManagedSettings blocks on messaging apps"}, "social_contacts": {"framework": "FamilyControls", "api_class": "AuthorizationCenter", "min_os": "16.0", "notes": "Requires FamilyControls entitlement; manages contact-based restrictions"}, "social_media_min_age": {"framework": "none", "api_class": "", "min_os": "", "notes": "Block social media BundleIDs via ManagedSettings for underage children"}, "social_multiplayer": {"framework": "ManagedSettings", "api_class": "ManagedSettingsStore.gameCenter.denyMultiplayerGaming", "min_os": "16.0"}, "targeted_ad_block": {"framework": "none", "api_class": "", "min_os": "", "notes": "App Tracking Transparency (ATT) is per-prompt; no blanket block API"}, "time_daily_limit": {"framework": "DeviceActivity", "api_class": "DeviceActivitySchedule", "min_os": "16.0", "notes": "Create a schedule with cumulative usage threshold"}, "time_downtime": {"framework": "DeviceActivity", "api_class": "DeviceActivitySchedule", "min_os": "16.0", "notes": "Block all apps outside allowed schedule window"}, "time_per_app_limit": {"framework": "DeviceActivity", "api_class": "DeviceActivitySchedule", "min_os": "16.0", "notes": "Per-ApplicationToken usage schedule"}, "time_scheduled_hours": {"framework": "DeviceActivity", "api_class": "DeviceActivitySchedule", "min_os": "16.0", "notes": "Schedule with start/end DateComponents for allowed hours"}, "usage_timer_notification": {"framework": "DeviceActivity", "api_class": "DeviceActivityMonitor", "min_os": "16.0",
                        "notes": "Fire local notification when usage interval reached"}, "web_category_block": {"framework": "ManagedSettings", "api_class": "ManagedSettingsStore.webContent.blockedByFilter", "min_os": "16.0"}, "web_custom_allowlist": {"framework": "ManagedSettings", "api_class": "ManagedSettingsStore.webContent.allowedDomains", "min_os": "16.0"}, "web_custom_blocklist": {"framework": "ManagedSettings", "api_class": "ManagedSettingsStore.webContent.blockedDomains", "min_os": "16.0"}, "web_filter_level": {"framework": "ManagedSettings", "api_class": "ManagedSettingsStore.webContent", "min_os": "16.0", "notes": "Map level → autoFilter (strict/moderate) or allowedDomains-only (strict)"}, "web_safesearch": {"framework": "ManagedSettings", "api_class": "ManagedSettingsStore.webContent.autoFilter", "min_os": "16.0"}}}
                android:
                  summary: "Android (GET /platform-mappings/android) — real sandbox capture"
                  value: {"age_ratings": {"AO": "RATING_18", "E": "RATING_3", "E10+": "RATING_7", "G": "RATING_3", "M": "RATING_16", "NC-17": "RATING_18", "PG": "RATING_7", "PG-13": "RATING_12", "R": "RATING_16", "T": "RATING_12", "TV-14": "RATING_12", "TV-G": "RATING_3", "TV-MA": "RATING_18", "TV-PG": "RATING_7", "TV-Y": "RATING_3", "TV-Y7": "RATING_7"}, "app_categories": {"dating": {"package_names": ["com.tinder", "com.bumble.app", "com.coffeemeetsbagel"]}, "gambling": {"package_names": ["com.draftkings.sportsbook", "com.fanduel.android.self"], "web_domains": ["draftkings.com", "fanduel.com", "betmgm.com"]}, "gaming": {"play_store_category": "GAME", "package_names": []}, "messaging": {"package_names": ["com.whatsapp", "org.telegram.messenger", "com.facebook.orca", "com.discord"]}, "social-media": {"play_store_category": "SOCIAL", "package_names": ["com.instagram.android", "com.twitter.android", "com.snapchat.android", "com.zhiliaoapp.musically", "com.facebook.katana"]}, "video-streaming": {"package_names": ["com.google.android.youtube", "com.netflix.mediaclient", "com.disney.disneyplus", "com.amazon.avod.thirdpartyclient"]}}, "system_apps": {"calendar": "com.google.android.calendar", "camera": "com.google.android.GoogleCamera", "clock": "com.google.android.deskclock", "contacts": "com.google.android.contacts", "find_my": "com.google.android.apps.adm", "maps": "com.google.android.apps.maps", "messages": "com.google.android.apps.messaging", "phone": "com.google.android.dialer", "photos": "com.google.android.apps.photos", "settings": "com.android.settings"}, "always_allowed": ["com.google.android.dialer", "com.google.android.apps.messaging", "com.google.android.apps.maps", "com.google.android.apps.adm", "com.android.settings"], "category_frameworks": {"addictive_design_control": {"framework": "AccessibilityService", "api_class": "onAccessibilityEvent", "min_sdk": 26, "notes": "Detect infinite scroll, autoplay patterns; overlay intervention UI"}, "age_gate": {"framework": "none", "api_class": "", "min_sdk": 0, "notes": "Age verification enforced server-side by Phosra; no Android API"}, "ai_minor_interaction": {"framework": "AccessibilityService", "api_class": "onAccessibilityEvent", "min_sdk": 26, "notes": "Monitor and restrict AI app interactions via accessibility service"}, "algo_feed_control": {"framework": "AccessibilityService", "api_class": "onAccessibilityEvent", "min_sdk": 26, "notes": "Detect algorithmic feed scrolling in social apps; overlay usage warning or block"}, "algorithmic_audit": {"framework": "none", "api_class": "", "min_sdk": 0, "notes": "Algorithmic transparency requirement — no Android enforcement API"}, "commercial_data_ban": {"framework": "none", "api_class": "", "min_sdk": 0, "notes": "Commercial data ban enforced server-side by Phosra; no Android API"}, "content_allow_title": {"framework": "DevicePolicyManager", "api_class": "setPackagesSuspended", "min_sdk": 28, "notes": "Invert to allowlist — suspend all except allowed packages"}, "content_allowlist_mode": {"framework": "DevicePolicyManager", "api_class": "setUserRestriction", "min_sdk": 26, "notes": "Set DISALLOW_INSTALL_APPS + allowlist specific packages"}, "content_block_title": {"framework": "DevicePolicyManager", "api_class": "setPackagesSuspended", "min_sdk": 28, "notes": "Suspend specific packages by name"}, "content_descriptor_block": {"framework": "none", "api_class": "", "min_sdk": 0, "notes": "Content descriptor filtering handled server-side by Phosra"}, "content_rating": {"framework": "PackageManager", "api_class": "setApplicationRestrictions", "min_sdk": 26, "notes": "Filter by Play Store content rating via managed configuration"}, "csam_reporting": {"framework": "none", "api_class": "", "min_sdk": 0, "notes": "CSAM detection/reporting handled server-side; Google SafeSearch integration via VPN"}, "data_deletion_request": {"framework": "none", "api_class": "", "min_sdk": 0, "notes": "Data deletion handled server-side; submit via Phosra app support flow"}, "dm_restriction": {"framework": "AccessibilityService", "api_class": "onAccessibilityEvent",
                        "min_sdk": 26, "notes": "Monitor DM activity in messaging apps; block or alert on restricted interactions"}, "geolocation_opt_in": {"framework": "ContentResolver", "api_class": "Settings.Secure", "min_sdk": 26, "notes": "Toggle per-app location permission via AppOpsManager or Settings.Secure"}, "image_rights_minor": {"framework": "none", "api_class": "", "min_sdk": 0, "notes": "Image rights enforcement is app-level — no Android framework controls this"}, "library_filter_compliance": {"framework": "VpnService", "api_class": "DNS filtering", "min_sdk": 26, "notes": "Web content filtering via local VPN satisfies CIPA-style requirements"}, "monitoring_activity": {"framework": "UsageStatsManager", "api_class": "queryUsageStats", "min_sdk": 26, "notes": "Aggregate app usage data via UsageStatsManager for parent dashboard"}, "monitoring_alerts": {"framework": "NotificationListenerService", "api_class": "onNotificationPosted", "min_sdk": 26, "notes": "Monitor child device notifications; forward flagged alerts to parent app"}, "notification_curfew": {"framework": "NotificationListenerService", "api_class": "cancelNotification", "min_sdk": 26, "notes": "Intercept and cancel notifications during curfew hours via NotificationListenerService"}, "parental_consent_gate": {"framework": "none", "api_class": "", "min_sdk": 0, "notes": "Parental consent gate handled via Phosra app UI flow; no Android framework needed"}, "parental_event_notification": {"framework": "NotificationManager", "api_class": "notify", "min_sdk": 26, "notes": "Push notification to parent device when flagged events are detected"}, "privacy_account_creation": {"framework": "DevicePolicyManager", "api_class": "addUserRestriction", "min_sdk": 26, "notes": "Set DISALLOW_MODIFY_ACCOUNTS user restriction to prevent new account creation"}, "privacy_data_sharing": {"framework": "DevicePolicyManager", "api_class": "setApplicationRestrictions", "min_sdk": 26, "notes": "Apply managed app config to restrict data sharing features"}, "privacy_location": {"framework": "ContentResolver", "api_class": "Settings.Secure", "min_sdk": 26, "notes": "Toggle location mode via Settings.Secure.LOCATION_MODE with DevicePolicyManager"}, "privacy_profile_visibility": {"framework": "none", "api_class": "", "min_sdk": 0, "notes": "App-level concern — no Android framework controls profile visibility"}, "purchase_approval": {"framework": "DevicePolicyManager", "api_class": "addUserRestriction", "min_sdk": 26, "notes": "Set DISALLOW_INSTALL_APPS user restriction to require parental approval"}, "purchase_block_iap": {"framework": "DevicePolicyManager", "api_class": "addUserRestriction", "min_sdk": 26, "notes": "Set DISALLOW_INSTALL_APPS to prevent app installs; IAP blocking requires app-level enforcement"}, "purchase_spending_cap": {"framework": "none", "api_class": "", "min_sdk": 0, "notes": "No Android API — spending cap tracked server-side by Phosra"}, "screen_time_report": {"framework": "UsageStatsManager", "api_class": "queryUsageStats", "min_sdk": 26, "notes": "Aggregate usage data into reports for parent dashboard"}, "social_chat_control": {"framework": "AccessibilityService", "api_class": "onAccessibilityEvent", "min_sdk": 26, "notes": "Monitor messaging app activity via accessibility events; block or alert on flagged interactions"}, "social_contacts": {"framework": "ContentResolver", "api_class": "ContactsContract", "min_sdk": 26, "notes": "Manage contact restrictions via ContactsContract content provider"}, "social_media_min_age": {"framework": "DevicePolicyManager", "api_class": "setPackagesSuspended", "min_sdk": 28, "notes": "Suspend social media packages for underage children"}, "social_multiplayer": {"framework": "DevicePolicyManager", "api_class": "setApplicationRestrictions", "min_sdk": 26, "notes": "Apply managed configuration to restrict multiplayer features in games"}, "targeted_ad_block": {"framework": "VpnService", "api_class": "DNS ad blocking", "min_sdk": 26, "notes": "Local VPN blocks known ad/tracker domains via DNS sinkhole"}, "time_daily_limit": {"framework": "UsageStatsManager",
                        "api_class": "queryUsageStats", "min_sdk": 26, "notes": "Query cumulative usage via UsageStatsManager; enforce limit with AlarmManager.setExact to trigger lock"}, "time_downtime": {"framework": "DevicePolicyManager", "api_class": "setLockTaskMode", "min_sdk": 26, "notes": "Lock device to a single allowed app (or launcher) outside allowed hours via AlarmManager scheduling"}, "time_per_app_limit": {"framework": "UsageStatsManager", "api_class": "queryUsageStats", "min_sdk": 26, "notes": "Per-package usage tracking; suspend package when limit reached"}, "time_scheduled_hours": {"framework": "AlarmManager", "api_class": "setExact", "min_sdk": 26, "notes": "Schedule start/end alarms for allowed usage windows"}, "usage_timer_notification": {"framework": "AlarmManager", "api_class": "setExact", "min_sdk": 26, "notes": "Schedule alarm at usage threshold; fire NotificationManager.notify to alert child/parent"}, "web_category_block": {"framework": "VpnService", "api_class": "DNS filtering", "min_sdk": 26, "notes": "Local VPN filters DNS by category using blocklist database"}, "web_custom_allowlist": {"framework": "VpnService", "api_class": "DNS allowlist", "min_sdk": 26, "notes": "Local VPN permits only allowlisted domains"}, "web_custom_blocklist": {"framework": "VpnService", "api_class": "DNS blocklist", "min_sdk": 26, "notes": "Local VPN blocks specific domains via DNS sinkhole"}, "web_filter_level": {"framework": "VpnService", "api_class": "DNS filter level", "min_sdk": 26, "notes": "Map filter level to DNS blocklist strictness: strict=allowlist-only, moderate=category block, off=passthrough"}, "web_safesearch": {"framework": "VpnService", "api_class": "DNS override", "min_sdk": 26, "notes": "Local VPN intercepts DNS; force SafeSearch IPs for Google/Bing/YouTube"}}}
        "404":
          description: Platform not supported
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: Not Found
                message: "platform mappings not available for: nintendo"
                code: 404

  # ── Sources ──────────────────────────────────────────────────────

        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '429':
          $ref: '#/components/responses/RateLimited'
        '502':
          $ref: '#/components/responses/BadGateway'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '500':
          $ref: '#/components/responses/ServerError'
  /sources/available:
    get:
      operationId: listAvailableSourceAdapters
      summary: List available source adapters
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.sources.listAvailable();
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/sources/available"
      - lang: Python
        label: Python
        source: |
          import requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/sources/available",
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/sources/available", nil)
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: List all available parental control source adapters that can be connected.
      tags: [Sources]
      security: []
      responses:
        "200":
          description: Available source list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/AvailableSource"

              example:
              - slug: four-norms
                display_name: NextDNS
                api_tier: managed
                auth_type: oauth2
                website: "https://nextdns.io"
                description: Managed content-filtering and screen-time source.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /sources:
    post:
      operationId: connectASource
      summary: Connect a source
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.sources.connect({
            child_id: "a11ce0fa-0000-4000-8000-0000000000a1",
            family_id: "f0000000-0000-4000-8000-0000000000f1",
            source: "fire_tablet",
            auto_sync: true
          });
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X POST "https://phosra-api-sandbox-production.up.railway.app/api/v1/sources" \
            -H "Authorization: Bearer $PHOSRA_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
            "child_id": "a11ce0fa-0000-4000-8000-0000000000a1",
            "family_id": "f0000000-0000-4000-8000-0000000000f1",
            "source": "fire_tablet",
            "auto_sync": true
          }'
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.post(
              f"{BASE}/sources",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
              json={
                  "child_id": "a11ce0fa-0000-4000-8000-0000000000a1",
                  "family_id": "f0000000-0000-4000-8000-0000000000f1",
                  "source": "fire_tablet",
                  "auto_sync": True
              },
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"bytes"
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	body := bytes.NewBufferString(`{
            "child_id": "a11ce0fa-0000-4000-8000-0000000000a1",
            "family_id": "f0000000-0000-4000-8000-0000000000f1",
            "source": "fire_tablet",
            "auto_sync": true
          }`)
          	req, _ := http.NewRequest("POST", base+"/sources", body)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	req.Header.Set("Content-Type", "application/json")
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: Connect a parental control app as a source for a child.
      tags: [Sources]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [child_id, family_id, source]
              properties:
                child_id:
                  type: string
                  format: uuid
                family_id:
                  type: string
                  format: uuid
                source:
                  type: string
                  description: "Source slug (e.g., 'bark', 'qustodio')"
                credentials:
                  type: object
                  description: Source credentials (for managed sources)
                auto_sync:
                  type: boolean
                  description: Enable automatic sync on policy changes
      responses:
        "201":
          description: Source connected
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Source"

              example:
                id: a2e6b9c1-3f74-4d08-9b25-6c1f0a8e4d73
                child_id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                family_id: fcdf4277-ba14-4732-8db5-0369b2c88d8b
                source_slug: nextdns
                display_name: NextDNS
                api_tier: managed
                status: pending
                auto_sync: true
                capabilities:
                - category: rating_age_gate
                  support_level: full
                  read_write: push_only
                  notes: Bidirectional sync supported; changes reconcile within ~60s
                config: {}
                last_sync_at: '2026-06-30T09:15:52Z'
                last_sync_status: success
                sync_version: 0
                error_message:
                created_at: '2026-06-18T14:32:07Z'
                updated_at: '2026-06-30T09:15:44Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /sources/{sourceID}:
    parameters:
    - name: sourceID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      operationId: getSourceDetails
      summary: Get source details
      description: "Returns a single connected source by ID, including its provider and sync configuration."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.sources.get("5a5a5a5a-1111-4222-8333-444455556666");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/sources/5a5a5a5a-1111-4222-8333-444455556666" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/sources/5a5a5a5a-1111-4222-8333-444455556666",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/sources/5a5a5a5a-1111-4222-8333-444455556666", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Sources]
      responses:
        "200":
          description: Source details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Source"
              example:
                id: a2e6b9c1-3f74-4d08-9b25-6c1f0a8e4d73
                child_id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                family_id: fcdf4277-ba14-4732-8db5-0369b2c88d8b
                source_slug: nextdns
                display_name: NextDNS
                api_tier: managed
                status: pending
                auto_sync: true
                capabilities:
                - category: rating_age_gate
                  support_level: full
                  read_write: push_only
                  notes: Bidirectional sync supported; changes reconcile within ~60s
                config: {}
                last_sync_at: '2026-06-30T09:15:52Z'
                last_sync_status: success
                sync_version: 0
                error_message:
                created_at: '2026-06-18T14:32:07Z'
                updated_at: '2026-06-30T09:15:44Z'
        "404":
          description: Source not found
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
    delete:
      operationId: disconnectSource
      summary: Disconnect source
      description: "Disconnects a source so Phosra stops ingesting data from it. Data already ingested is retained."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          await phosra.sources.disconnect("5a5a5a5a-1111-4222-8333-444455556666");
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X DELETE "https://phosra-api-sandbox-production.up.railway.app/api/v1/sources/5a5a5a5a-1111-4222-8333-444455556666" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.delete(
              f"{BASE}/sources/5a5a5a5a-1111-4222-8333-444455556666",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("DELETE", base+"/sources/5a5a5a5a-1111-4222-8333-444455556666", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Sources]
      responses:
        "204":
          description: Source disconnected
        "404":
          description: Source not found

        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /sources/{sourceID}/sync:
    parameters:
    - name: sourceID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      operationId: syncSource
      summary: Sync source
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.sources.sync("5a5a5a5a-1111-4222-8333-444455556666");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X POST "https://phosra-api-sandbox-production.up.railway.app/api/v1/sources/5a5a5a5a-1111-4222-8333-444455556666/sync" \
            -H "Authorization: Bearer $PHOSRA_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
            "sync_mode": "incremental"
          }'
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.post(
              f"{BASE}/sources/5a5a5a5a-1111-4222-8333-444455556666/sync",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
              json={
                  "sync_mode": "incremental"
              },
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"bytes"
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	body := bytes.NewBufferString(`{
            "sync_mode": "incremental"
          }`)
          	req, _ := http.NewRequest("POST", base+"/sources/5a5a5a5a-1111-4222-8333-444455556666/sync", body)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	req.Header.Set("Content-Type", "application/json")
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: Push all active policy rules to a connected source.
      tags: [Sources]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                sync_mode:
                  type: string
                  enum: [full, incremental]
                  description: "Sync mode (default: full)"
      responses:
        "202":
          description: Sync job started
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SourceSyncJob"

              example:
                id: 4d7b0e9a-1c86-42f3-95d0-7a2e6b1f8c34
                source_id: a2e6b9c1-3f74-4d08-9b25-6c1f0a8e4d73
                sync_mode: full
                trigger_type: manual
                status: pending
                rules_pushed: 0
                rules_skipped: 0
                rules_failed: 0
                error_message:
                started_at: '2026-06-30T09:15:44Z'
                completed_at: '2026-06-30T09:15:52Z'
                created_at: '2026-06-18T14:32:07Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '502':
          $ref: '#/components/responses/BadGateway'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '500':
          $ref: '#/components/responses/ServerError'
  /sources/{sourceID}/rules:
    parameters:
    - name: sourceID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      operationId: pushSingleRule
      summary: Push single rule
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.sources.pushRule("5a5a5a5a-1111-4222-8333-444455556666", {
            category: "time_daily_limit",
            value: {
              minutes: 120
            }
          });
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X POST "https://phosra-api-sandbox-production.up.railway.app/api/v1/sources/5a5a5a5a-1111-4222-8333-444455556666/rules" \
            -H "Authorization: Bearer $PHOSRA_API_KEY" \
            -H "Content-Type: application/json" \
            -d '{
            "category": "time_daily_limit",
            "value": {
              "minutes": 120
            }
          }'
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.post(
              f"{BASE}/sources/5a5a5a5a-1111-4222-8333-444455556666/rules",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
              json={
                  "category": "time_daily_limit",
                  "value": {
                      "minutes": 120
                  }
              },
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"bytes"
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	body := bytes.NewBufferString(`{
            "category": "time_daily_limit",
            "value": {
              "minutes": 120
            }
          }`)
          	req, _ := http.NewRequest("POST", base+"/sources/5a5a5a5a-1111-4222-8333-444455556666/rules", body)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	req.Header.Set("Content-Type", "application/json")
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: Push a single rule to a connected source.
      tags: [Sources]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [category]
              properties:
                category:
                  $ref: "#/components/schemas/RuleCategory"
                value:
                  type: object
                  description: Rule value/config
      responses:
        "200":
          description: Rule push result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SourceSyncResult"

              example:
                id: d60b3e9a-7c14-42f8-95d1-8a0e6b2f7c43
                job_id: 4d7b0e9a-1c86-42f3-95d0-7a2e6b1f8c34
                source_id: a2e6b9c1-3f74-4d08-9b25-6c1f0a8e4d73
                rule_category: rating_age_gate
                status: pushed
                source_value: {}
                source_response: {}
                error_message:
                created_at: '2026-06-18T14:32:07Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /sources/{sourceID}/guide/{category}:
    parameters:
    - name: sourceID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    - name: category
      in: path
      required: true
      schema:
        type: string
    get:
      operationId: getGuidedSteps
      summary: Get guided steps
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.sources.getGuidedSteps("5a5a5a5a-1111-4222-8333-444455556666", "screen_time");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/sources/5a5a5a5a-1111-4222-8333-444455556666/guide/screen_time" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/sources/5a5a5a5a-1111-4222-8333-444455556666/guide/screen_time",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/sources/5a5a5a5a-1111-4222-8333-444455556666/guide/screen_time", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: Get manual setup instructions for a guided-tier source.
      tags: [Sources]
      responses:
        "200":
          description: Guided steps
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/GuidedStep"
              example:
              - step_number: 0
                title: Sign in to NextDNS
                description: Managed content-filtering and screen-time source.
                image_url: https://cdn.phosra.com/guides/nextdns-step-1.png
                deep_link: "nextdns://settings/parental-controls"
        "404":
          description: Source or category not found

        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /sources/{sourceID}/jobs:
    parameters:
    - name: sourceID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      operationId: listSyncJobs
      summary: List sync jobs
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.sources.listSyncJobs("5a5a5a5a-1111-4222-8333-444455556666");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/sources/5a5a5a5a-1111-4222-8333-444455556666/jobs" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/sources/5a5a5a5a-1111-4222-8333-444455556666/jobs",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/sources/5a5a5a5a-1111-4222-8333-444455556666/jobs", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: List sync job history for a source.
      tags: [Sources]
      parameters:
      - name: limit
        in: query
        description: Maximum number of jobs to return
        schema:
          type: integer
      responses:
        "200":
          description: Sync job list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/SourceSyncJob"

              example:
              - id: 4d7b0e9a-1c86-42f3-95d0-7a2e6b1f8c34
                source_id: a2e6b9c1-3f74-4d08-9b25-6c1f0a8e4d73
                sync_mode: full
                trigger_type: manual
                status: pending
                rules_pushed: 0
                rules_skipped: 0
                rules_failed: 0
                error_message:
                started_at: '2026-06-30T09:15:44Z'
                completed_at: '2026-06-30T09:15:52Z'
                created_at: '2026-06-18T14:32:07Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /sources/{sourceID}/jobs/{jobID}:
    parameters:
    - name: sourceID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    - name: jobID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      operationId: getSyncJob
      summary: Get sync job
      description: "Returns the status and results of a source sync job by ID."
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.sources.getSyncJob("5a5a5a5a-1111-4222-8333-444455556666", "9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/sources/5a5a5a5a-1111-4222-8333-444455556666/jobs/9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/sources/5a5a5a5a-1111-4222-8333-444455556666/jobs/9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/sources/5a5a5a5a-1111-4222-8333-444455556666/jobs/9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      tags: [Sources]
      responses:
        "200":
          description: Sync job details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SourceSyncJob"
              example:
                id: 4d7b0e9a-1c86-42f3-95d0-7a2e6b1f8c34
                source_id: a2e6b9c1-3f74-4d08-9b25-6c1f0a8e4d73
                sync_mode: full
                trigger_type: manual
                status: pending
                rules_pushed: 0
                rules_skipped: 0
                rules_failed: 0
                error_message:
                started_at: '2026-06-30T09:15:44Z'
                completed_at: '2026-06-30T09:15:52Z'
                created_at: '2026-06-18T14:32:07Z'
        "404":
          description: Sync job not found

        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /sources/{sourceID}/jobs/{jobID}/results:
    parameters:
    - name: sourceID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    - name: jobID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      operationId: getSyncResults
      summary: Get sync results
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.sources.getSyncResults("5a5a5a5a-1111-4222-8333-444455556666", "9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/sources/5a5a5a5a-1111-4222-8333-444455556666/jobs/9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6/results" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/sources/5a5a5a5a-1111-4222-8333-444455556666/jobs/9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6/results",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/sources/5a5a5a5a-1111-4222-8333-444455556666/jobs/9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6/results", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: Get per-rule results for a sync job.
      tags: [Sources]
      responses:
        "200":
          description: Sync result list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/SourceSyncResult"

              example:
              - id: d60b3e9a-7c14-42f8-95d1-8a0e6b2f7c43
                job_id: 4d7b0e9a-1c86-42f3-95d0-7a2e6b1f8c34
                source_id: a2e6b9c1-3f74-4d08-9b25-6c1f0a8e4d73
                rule_category: rating_age_gate
                status: pushed
                source_value: {}
                source_response: {}
                error_message:
                created_at: '2026-06-18T14:32:07Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /sources/{sourceID}/jobs/{jobID}/retry:
    parameters:
    - name: sourceID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    - name: jobID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      operationId: retrySync
      summary: Retry sync
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.sources.retrySyncJob("5a5a5a5a-1111-4222-8333-444455556666", "9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS -X POST "https://phosra-api-sandbox-production.up.railway.app/api/v1/sources/5a5a5a5a-1111-4222-8333-444455556666/jobs/9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6/retry" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.post(
              f"{BASE}/sources/5a5a5a5a-1111-4222-8333-444455556666/jobs/9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6/retry",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("POST", base+"/sources/5a5a5a5a-1111-4222-8333-444455556666/jobs/9b2f4a1e-5c3d-4e2a-8f1b-2c963f66afa6/retry", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: Retry a failed sync job.
      tags: [Sources]
      responses:
        "202":
          description: Retry job started
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SourceSyncJob"
              example:
                id: 4d7b0e9a-1c86-42f3-95d0-7a2e6b1f8c34
                source_id: a2e6b9c1-3f74-4d08-9b25-6c1f0a8e4d73
                sync_mode: full
                trigger_type: manual
                status: pending
                rules_pushed: 0
                rules_skipped: 0
                rules_failed: 0
                error_message:
                started_at: '2026-06-30T09:15:44Z'
                completed_at: '2026-06-30T09:15:52Z'
                created_at: '2026-06-18T14:32:07Z'
        "404":
          description: Sync job not found

        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /children/{childID}/sources:
    parameters:
    - name: childID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      operationId: listSourcesForChild
      summary: List sources for child
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.sources.listByChild("a11ce0fa-0000-4000-8000-0000000000a1");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/children/a11ce0fa-0000-4000-8000-0000000000a1/sources" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/children/a11ce0fa-0000-4000-8000-0000000000a1/sources",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/children/a11ce0fa-0000-4000-8000-0000000000a1/sources", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: List all connected sources for a child.
      tags: [Sources]
      responses:
        "200":
          description: Source list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Source"

              example:
              - id: a2e6b9c1-3f74-4d08-9b25-6c1f0a8e4d73
                child_id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                family_id: fcdf4277-ba14-4732-8db5-0369b2c88d8b
                source_slug: nextdns
                display_name: NextDNS
                api_tier: managed
                status: pending
                auto_sync: true
                capabilities:
                - category: rating_age_gate
                  support_level: full
                  read_write: push_only
                  notes: Bidirectional sync supported; changes reconcile within ~60s
                config: {}
                last_sync_at: '2026-06-30T09:15:52Z'
                last_sync_status: success
                sync_version: 0
                error_message:
                created_at: '2026-06-18T14:32:07Z'
                updated_at: '2026-06-30T09:15:44Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
  /families/{familyID}/sources:
    parameters:
    - name: familyID
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      operationId: listSourcesForFamily
      summary: List sources for family
      x-codeSamples:
      - lang: JavaScript
        label: SDK (@phosra/sdk)
        source: |
          import { PhosraClient } from "@phosra/sdk";

          const phosra = new PhosraClient({
            baseUrl: "https://phosra-api-sandbox-production.up.railway.app/api/v1",
            accessToken: process.env.PHOSRA_API_KEY,
          });

          const result = await phosra.sources.listByFamily("f0000000-0000-4000-8000-0000000000f1");
          console.log(result);
      - lang: Shell
        label: cURL
        source: |
          curl -sS "https://phosra-api-sandbox-production.up.railway.app/api/v1/families/f0000000-0000-4000-8000-0000000000f1/sources" \
            -H "Authorization: Bearer $PHOSRA_API_KEY"
      - lang: Python
        label: Python
        source: |
          import os, requests

          BASE = "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          res = requests.get(
              f"{BASE}/families/f0000000-0000-4000-8000-0000000000f1/sources",
              headers={"Authorization": f"Bearer {os.environ['PHOSRA_API_KEY']}"},
          )
          print(res.status_code, res.json())
      - lang: Go
        label: Go
        source: |
          package main

          import (
          	"fmt"
          	"io"
          	"net/http"
          	"os"
          )

          func main() {
          	base := "https://phosra-api-sandbox-production.up.railway.app/api/v1"
          	req, _ := http.NewRequest("GET", base+"/families/f0000000-0000-4000-8000-0000000000f1/sources", nil)
          	req.Header.Set("Authorization", "Bearer "+os.Getenv("PHOSRA_API_KEY"))
          	resp, err := http.DefaultClient.Do(req)
          	if err != nil {
          		panic(err)
          	}
          	defer resp.Body.Close()
          	out, _ := io.ReadAll(resp.Body)
          	fmt.Println(resp.Status, string(out))
          }
      description: List all connected sources for a family.
      tags: [Sources]
      responses:
        "200":
          description: Source list
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Source"
              example:
              - id: a2e6b9c1-3f74-4d08-9b25-6c1f0a8e4d73
                child_id: ca02f5d1-fee8-4201-b5ca-fdc8be506e1e
                family_id: fcdf4277-ba14-4732-8db5-0369b2c88d8b
                source_slug: nextdns
                display_name: NextDNS
                api_tier: managed
                status: pending
                auto_sync: true
                capabilities:
                - category: rating_age_gate
                  support_level: full
                  read_write: push_only
                  notes: Bidirectional sync supported; changes reconcile within ~60s
                config: {}
                last_sync_at: '2026-06-30T09:15:52Z'
                last_sync_status: success
                sync_version: 0
                error_message:
                created_at: '2026-06-18T14:32:07Z'
                updated_at: '2026-06-30T09:15:44Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
